From f1d6dc4b32e40f231c8d17965b40274ff64f617c Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Sat, 28 Mar 2026 21:52:34 -0400 Subject: [PATCH 1/2] Add .worktrees/ to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d1f6ee3..942c01a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ *.coverage .env .angelusvigil/ +.worktrees/ PROJECTS/beginner/hash-cracker/docs/ From 0bfdd8d247ea108dfee249bcd7fd698b4c5eaef3 Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Mon, 30 Mar 2026 15:28:16 -0400 Subject: [PATCH 2/2] project binary-analysis-tool complete --- .../binary-analysis-tool/.env.example | 19 + .../binary-analysis-tool/.gitignore | 24 + .../binary-analysis-tool/README.md | 65 + .../binary-analysis-tool/backend/Cargo.lock | 4442 +++++++++++++++++ .../binary-analysis-tool/backend/Cargo.toml | 20 + .../crates/axumortem-engine/Cargo.toml | 26 + .../crates/axumortem-engine/src/context.rs | 66 + .../crates/axumortem-engine/src/error.rs | 33 + .../axumortem-engine/src/formats/elf.rs | 313 ++ .../axumortem-engine/src/formats/macho.rs | 361 ++ .../axumortem-engine/src/formats/mod.rs | 335 ++ .../crates/axumortem-engine/src/formats/pe.rs | 332 ++ .../crates/axumortem-engine/src/lib.rs | 76 + .../crates/axumortem-engine/src/pass.rs | 386 ++ .../axumortem-engine/src/passes/disasm.rs | 948 ++++ .../axumortem-engine/src/passes/entropy.rs | 540 ++ .../axumortem-engine/src/passes/format.rs | 183 + .../axumortem-engine/src/passes/imports.rs | 908 ++++ .../crates/axumortem-engine/src/passes/mod.rs | 9 + .../axumortem-engine/src/passes/strings.rs | 897 ++++ .../axumortem-engine/src/passes/threat.rs | 1388 +++++ .../crates/axumortem-engine/src/types.rs | 161 + .../crates/axumortem-engine/src/yara.rs | 493 ++ .../axumortem-engine/tests/fixtures/hello_elf | Bin 0 -> 15832 bytes .../tests/fixtures/hello_elf_stripped | Bin 0 -> 14384 bytes .../axumortem-engine/tests/integration.rs | 93 + .../backend/crates/axumortem/Cargo.toml | 31 + .../axumortem/migrations/001_initial.sql | 31 + .../backend/crates/axumortem/src/config.rs | 33 + .../backend/crates/axumortem/src/db/mod.rs | 13 + .../backend/crates/axumortem/src/db/models.rs | 50 + .../crates/axumortem/src/db/queries.rs | 92 + .../backend/crates/axumortem/src/error.rs | 100 + .../backend/crates/axumortem/src/main.rs | 95 + .../crates/axumortem/src/middleware/cors.rs | 29 + .../crates/axumortem/src/middleware/mod.rs | 4 + .../crates/axumortem/src/routes/analysis.rs | 66 + .../crates/axumortem/src/routes/health.rs | 30 + .../crates/axumortem/src/routes/mod.rs | 21 + .../crates/axumortem/src/routes/upload.rs | 180 + .../backend/crates/axumortem/src/state.rs | 16 + .../cloudflared.compose.yml | 27 + .../binary-analysis-tool/compose.yml | 92 + .../binary-analysis-tool/dev.compose.yml | 95 + .../frontend/.dockerignore | 15 + .../binary-analysis-tool/frontend/.gitignore | 25 + .../frontend/.stylelintignore | 22 + .../binary-analysis-tool/frontend/biome.json | 94 + .../binary-analysis-tool/frontend/index.html | 44 + .../frontend/package.json | 51 + .../frontend/pnpm-lock.yaml | 2636 ++++++++++ .../public/assets/android-chrome-192x192.png | Bin 0 -> 41495 bytes .../public/assets/android-chrome-512x512.png | Bin 0 -> 225933 bytes .../public/assets/apple-touch-icon.png | Bin 0 -> 37679 bytes .../frontend/public/assets/favicon-16x16.png | Bin 0 -> 600 bytes .../frontend/public/assets/favicon-32x32.png | Bin 0 -> 1838 bytes .../frontend/public/assets/favicon.ico | Bin 0 -> 15406 bytes .../frontend/public/assets/site.webmanifest | 1 + .../binary-analysis-tool/frontend/src/App.tsx | 36 + .../frontend/src/api/hooks/index.ts | 48 + .../frontend/src/api/index.ts | 8 + .../frontend/src/api/schemas.ts | 397 ++ .../frontend/src/api/types/index.ts | 116 + .../frontend/src/config.ts | 76 + .../frontend/src/core/api/api.config.ts | 17 + .../frontend/src/core/api/errors.ts | 114 + .../frontend/src/core/api/index.ts | 8 + .../frontend/src/core/api/query.config.ts | 105 + .../frontend/src/core/app/routers.tsx | 30 + .../frontend/src/core/app/shell.module.scss | 331 ++ .../frontend/src/core/app/shell.tsx | 36 + .../frontend/src/core/app/toast.module.scss | 67 + .../frontend/src/core/lib/format.ts | 41 + .../frontend/src/core/lib/index.ts | 7 + .../frontend/src/core/lib/shell.ui.store.ts | 63 + .../frontend/src/main.tsx | 15 + .../src/pages/analysis/analysis.module.scss | 1129 +++++ .../frontend/src/pages/analysis/index.tsx | 195 + .../src/pages/analysis/tab-disassembly.tsx | 304 ++ .../src/pages/analysis/tab-entropy.tsx | 110 + .../src/pages/analysis/tab-headers.tsx | 315 ++ .../src/pages/analysis/tab-imports.tsx | 205 + .../src/pages/analysis/tab-overview.tsx | 144 + .../src/pages/analysis/tab-strings.tsx | 233 + .../frontend/src/pages/landing/index.tsx | 205 + .../src/pages/landing/landing.module.scss | 443 ++ .../frontend/src/styles.scss | 29 + .../frontend/src/styles/_fonts.scss | 12 + .../frontend/src/styles/_index.scss | 8 + .../frontend/src/styles/_mixins.scss | 120 + .../frontend/src/styles/_reset.scss | 198 + .../frontend/src/styles/_tokens.scss | 177 + .../frontend/stylelint.config.js | 107 + .../frontend/tsconfig.app.json | 31 + .../frontend/tsconfig.json | 7 + .../frontend/tsconfig.node.json | 24 + .../frontend/vite.config.ts | 71 + .../infra/docker/rust.dev | 18 + .../infra/docker/rust.prod | 46 + .../infra/docker/vite.dev | 25 + .../infra/docker/vite.prod | 59 + .../infra/nginx/dev.nginx | 45 + .../infra/nginx/nginx.conf | 81 + .../infra/nginx/nginx.prod.conf | 70 + .../infra/nginx/prod.nginx | 64 + .../binary-analysis-tool/justfile | 159 + .../binary-analysis-tool/learn/00-OVERVIEW.md | 158 + .../binary-analysis-tool/learn/01-CONCEPTS.md | 564 +++ .../learn/02-ARCHITECTURE.md | 588 +++ .../learn/03-IMPLEMENTATION.md | 857 ++++ .../learn/04-CHALLENGES.md | 448 ++ .../binary-analysis-tool/scripts/init.sh | 87 + .../scripts/randomize-ports.sh | 56 + .../test-specimens/clean_hello.c | 50 + .../test-specimens/clean_hello_debug | Bin 0 -> 18520 bytes .../test-specimens/clean_hello_static | Bin 0 -> 758560 bytes .../test-specimens/clean_hello_stripped | Bin 0 -> 14496 bytes .../test-specimens/crypto_tool | Bin 0 -> 22144 bytes .../test-specimens/crypto_tool.c | 154 + .../test-specimens/crypto_tool_stripped | Bin 0 -> 14568 bytes .../test-specimens/many_imports | Bin 0 -> 17360 bytes .../test-specimens/many_imports.c | 137 + .../test-specimens/packed_stub | Bin 0 -> 19024 bytes .../test-specimens/packed_stub.c | 84 + .../test-specimens/suspicious_beacon | Bin 0 -> 22936 bytes .../test-specimens/suspicious_beacon.c | 137 + .../test-specimens/suspicious_beacon_stripped | Bin 0 -> 14656 bytes 127 files changed, 25480 insertions(+) create mode 100644 PROJECTS/intermediate/binary-analysis-tool/.env.example create mode 100644 PROJECTS/intermediate/binary-analysis-tool/.gitignore create mode 100644 PROJECTS/intermediate/binary-analysis-tool/README.md create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.lock create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.toml create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/Cargo.toml create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/context.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/error.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/elf.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/macho.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/mod.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/pe.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/lib.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/pass.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/disasm.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/entropy.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/format.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/imports.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/mod.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/strings.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/threat.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/types.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/yara.rs create mode 100755 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/tests/fixtures/hello_elf create mode 100755 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/tests/fixtures/hello_elf_stripped create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/tests/integration.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/Cargo.toml create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/migrations/001_initial.sql create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/config.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/mod.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/models.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/queries.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/error.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/main.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/cors.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/mod.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/analysis.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/health.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/mod.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/upload.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/state.rs create mode 100644 PROJECTS/intermediate/binary-analysis-tool/cloudflared.compose.yml create mode 100644 PROJECTS/intermediate/binary-analysis-tool/compose.yml create mode 100644 PROJECTS/intermediate/binary-analysis-tool/dev.compose.yml create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/.dockerignore create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/.gitignore create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/.stylelintignore create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/biome.json create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/index.html create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/package.json create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/pnpm-lock.yaml create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/android-chrome-192x192.png create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/android-chrome-512x512.png create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/apple-touch-icon.png create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/favicon-16x16.png create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/favicon-32x32.png create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/favicon.ico create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/site.webmanifest create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/App.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/hooks/index.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/index.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/schemas.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/types/index.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/config.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/api.config.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/errors.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/index.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/query.config.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/routers.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.module.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/toast.module.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/format.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/index.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/shell.ui.store.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/main.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/analysis.module.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/index.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-disassembly.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-entropy.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-headers.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-imports.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-overview.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-strings.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/landing/index.tsx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/landing/landing.module.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/styles.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/styles/_fonts.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/styles/_index.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/styles/_mixins.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/styles/_reset.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/src/styles/_tokens.scss create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/stylelint.config.js create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/tsconfig.app.json create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/tsconfig.json create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/tsconfig.node.json create mode 100644 PROJECTS/intermediate/binary-analysis-tool/frontend/vite.config.ts create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/docker/rust.dev create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/docker/rust.prod create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/docker/vite.dev create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/docker/vite.prod create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/nginx/dev.nginx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/nginx/nginx.conf create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/nginx/nginx.prod.conf create mode 100644 PROJECTS/intermediate/binary-analysis-tool/infra/nginx/prod.nginx create mode 100644 PROJECTS/intermediate/binary-analysis-tool/justfile create mode 100644 PROJECTS/intermediate/binary-analysis-tool/learn/00-OVERVIEW.md create mode 100644 PROJECTS/intermediate/binary-analysis-tool/learn/01-CONCEPTS.md create mode 100644 PROJECTS/intermediate/binary-analysis-tool/learn/02-ARCHITECTURE.md create mode 100644 PROJECTS/intermediate/binary-analysis-tool/learn/03-IMPLEMENTATION.md create mode 100644 PROJECTS/intermediate/binary-analysis-tool/learn/04-CHALLENGES.md create mode 100755 PROJECTS/intermediate/binary-analysis-tool/scripts/init.sh create mode 100755 PROJECTS/intermediate/binary-analysis-tool/scripts/randomize-ports.sh create mode 100644 PROJECTS/intermediate/binary-analysis-tool/test-specimens/clean_hello.c create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/clean_hello_debug create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/clean_hello_static create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/clean_hello_stripped create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/crypto_tool create mode 100644 PROJECTS/intermediate/binary-analysis-tool/test-specimens/crypto_tool.c create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/crypto_tool_stripped create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/many_imports create mode 100644 PROJECTS/intermediate/binary-analysis-tool/test-specimens/many_imports.c create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/packed_stub create mode 100644 PROJECTS/intermediate/binary-analysis-tool/test-specimens/packed_stub.c create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/suspicious_beacon create mode 100644 PROJECTS/intermediate/binary-analysis-tool/test-specimens/suspicious_beacon.c create mode 100755 PROJECTS/intermediate/binary-analysis-tool/test-specimens/suspicious_beacon_stripped diff --git a/PROJECTS/intermediate/binary-analysis-tool/.env.example b/PROJECTS/intermediate/binary-analysis-tool/.env.example new file mode 100644 index 0000000..6c3bc7f --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/.env.example @@ -0,0 +1,19 @@ +# ©AngelaMos | 2026 +# .env.example +# Copy to .env (production) or .env.development (dev) and adjust values + +APP_NAME=axumortem +VITE_API_URL=/api +VITE_APP_TITLE=axumortem + +NGINX_HOST_PORT=22784 +FRONTEND_HOST_PORT=15723 +BACKEND_HOST_PORT=3000 +POSTGRES_HOST_PORT=5432 + +POSTGRES_PASSWORD=changeme +RUST_LOG=info +MAX_UPLOAD_SIZE=52428800 +CORS_ORIGIN=* + +# CLOUDFLARE_TUNNEL_TOKEN=your-token-here diff --git a/PROJECTS/intermediate/binary-analysis-tool/.gitignore b/PROJECTS/intermediate/binary-analysis-tool/.gitignore new file mode 100644 index 0000000..97c020f --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/.gitignore @@ -0,0 +1,24 @@ +# ©AngelaMos | 2026 +# .gitignore + +# Rust +backend/target/ + +# Environment +.env +.env.development +.env.production +.env.local +!.env.example + +# Development docs +docs/ + +# OS +.DS_Store +Thumbs.db + +# Editors +.idea/ +.vscode/ +*.sw? diff --git a/PROJECTS/intermediate/binary-analysis-tool/README.md b/PROJECTS/intermediate/binary-analysis-tool/README.md new file mode 100644 index 0000000..23d0bf0 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/README.md @@ -0,0 +1,65 @@ +```rust + █████╗ ██╗ ██╗██╗ ██╗███╗ ███╗ ██████╗ ██████╗ ████████╗███████╗███╗ ███╗ +██╔══██╗╚██╗██╔╝██║ ██║████╗ ████║██╔═══██╗██╔══██╗╚══██╔══╝██╔════╝████╗ ████║ +███████║ ╚███╔╝ ██║ ██║██╔████╔██║██║ ██║██████╔╝ ██║ █████╗ ██╔████╔██║ +██╔══██║ ██╔██╗ ██║ ██║██║╚██╔╝██║██║ ██║██╔══██╗ ██║ ██╔══╝ ██║╚██╔╝██║ +██║ ██║██╔╝ ██╗╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║ ██║ ██║ ███████╗██║ ╚═╝ ██║ +╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ +``` + +[![Cybersecurity Projects](https://img.shields.io/badge/Cybersecurity--Projects-Project%20%2320-red?style=flat&logo=github)](https://github.com/CarterPerez-dev/Cybersecurity-Projects/tree/main/PROJECTS/intermediate/binary-analysis-tool) +[![Rust](https://img.shields.io/badge/Rust-stable-000000?style=flat&logo=rust&logoColor=white)](https://www.rust-lang.org) +[![React](https://img.shields.io/badge/React-19-61DAFB?style=flat&logo=react&logoColor=black)](https://react.dev) +[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org) +[![License: AGPLv3](https://img.shields.io/badge/License-AGPL_v3-purple.svg)](https://www.gnu.org/licenses/agpl-3.0) +[![Docker](https://img.shields.io/badge/Docker-ready-2496ED?style=flat&logo=docker)](https://www.docker.com) + +> Static binary analysis engine with multi-format parsing, YARA scanning, x86 disassembly, and MITRE ATT&CK threat scoring. + +*This is a quick overview — security theory, architecture, and full walkthroughs are in the [learn modules](#learn).* + +## What It Does + +- Multi-format binary parsing (ELF, PE, Mach-O) with section analysis and import table extraction +- YARA rule scanning with 14 built-in detection rules for malware, packers, and crypto patterns +- x86/x86_64 disassembly with control flow graph generation from entry points and symbol tables +- Shannon entropy analysis for detecting packed or encrypted sections +- 8-category threat scoring system (max 100 points) with MITRE ATT&CK technique mapping +- Pass-based analysis pipeline with topological ordering and dependency resolution + +## Quick Start + +```bash +docker compose up -d +``` + +Visit `http://localhost:22784` + +> [!TIP] +> This project uses [`just`](https://github.com/casey/just) as a command runner. Type `just` to see all available commands. +> +> Install: `curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin` + +## Stack + +**Backend:** Rust, Axum, goblin, iced-x86, yara-x, SQLx, PostgreSQL + +**Frontend:** React 19, TypeScript, Vite, TanStack Query, Zustand, Zod, SCSS Modules + +**Infra:** Docker Compose, Nginx, PostgreSQL 18 + +## Learn + +This project includes step-by-step learning materials covering security theory, architecture, and implementation. + +| Module | Topic | +|--------|-------| +| [00 - Overview](learn/00-OVERVIEW.md) | Prerequisites and quick start | +| [01 - Concepts](learn/01-CONCEPTS.md) | Security theory and real-world breaches | +| [02 - Architecture](learn/02-ARCHITECTURE.md) | System design and data flow | +| [03 - Implementation](learn/03-IMPLEMENTATION.md) | Code walkthrough | +| [04 - Challenges](learn/04-CHALLENGES.md) | Extension ideas and exercises | + +## License + +AGPL 3.0 diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.lock b/PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.lock new file mode 100644 index 0000000..1419ef2 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.lock @@ -0,0 +1,4442 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "log", + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object 0.37.3", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "array-bytes" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d55334c98d756b32dcceb60248647ab34f027690f87f9a362fd292676ee927" +dependencies = [ + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "ascii_tree" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6c635b3aa665c649ad1415f1573c85957dfa47690ec27aebe7ec17efe3c643" + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "multer", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axumortem" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "axumortem-engine", + "chrono", + "clap", + "serde", + "serde_json", + "sqlx", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "axumortem-engine" +version = "0.1.0" +dependencies = [ + "goblin", + "iced-x86", + "memmap2", + "petgraph", + "rangemap", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.18", + "tracing", + "yara-x", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-bitset" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-codegen" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli 0.31.1", + "hashbrown 0.14.5", + "log", + "regalloc2", + "rustc-hash 2.1.2", + "serde", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb" + +[[package]] +name = "cranelift-control" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-frontend" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d" + +[[package]] +name = "cranelift-native" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dsa" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +dependencies = [ + "digest", + "num-bigint-dig", + "num-traits", + "pkcs8", + "rfc6979", + "sha2", + "signature", + "zeroize", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fmmap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099ab52d5329340a3014f60ca91bc892181ae32e752360d07be9295924dcb0b" +dependencies = [ + "byteorder", + "bytes", + "enum_dispatch", + "fs4", + "memmapix", + "parse-display", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs4" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" +dependencies = [ + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator 0.2.0", + "indexmap 1.9.3", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator 0.3.0", + "indexmap 2.13.0", + "stable_deref_trait", +] + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax 0.8.10", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + +[[package]] +name = "goblin" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa0a64d21a7eb230583b4c5f4e23b7e4e57974f96620f42a7e75e08ae66d745" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iced-x86" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "intaglio" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4271d1532513fd3671b4a768fc5f189d5365621d2fb844cfb341ef4b67afaff" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linkme" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e3283ed2d0e50c06dd8602e0ab319bb048b6325d0bba739db64ed8205179898" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "logos" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax 0.8.10", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "md2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4f0f3ed25ff4f8d8d102288d92f900efc202661c884cf67dfe4f0d07c43d1f" +dependencies = [ + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.4", +] + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memmapix" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f517ab414225d5f1755bd284d9545bd08a72a3958b3c6384d72e95de9cc1a1d3" +dependencies = [ + "rustix 0.38.44", +] + +[[package]] +name = "memx" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93022fcc0eab2dc9dd134e362592e06f0b3c0aa549ae91d8b3e539e56c2a0687" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "memchr", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "parse-display" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6509d08722b53e8dafe97f2027b22ccbe3a5db83cb352931e9716b0aa44bc5c" +dependencies = [ + "once_cell", + "parse-display-derive", + "regex", +] + +[[package]] +name = "parse-display-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68517892c8daf78da08c0db777fcc17e07f2f63ef70041718f8a7630ad84f341" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "regex", + "regex-syntax 0.7.5", + "structmeta", + "syn", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.13.0", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-codegen" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3976825c0014bbd2f3b34f0001876604fe87e0c86cd8fa54251530f1544ace" +dependencies = [ + "anyhow", + "once_cell", + "protobuf", + "protobuf-parse", + "regex", + "tempfile", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-parse" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "log", + "protobuf", + "protobuf-support", + "tempfile", + "thiserror 1.0.69", + "which", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + +[[package]] +name = "psm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "pulley-interpreter" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d95f8575df49a2708398182f49a888cf9dc30210fb1fd2df87c889edcee75d" +dependencies = [ + "cranelift-bitset", + "log", + "sptr", + "wasmtime-math", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regalloc2" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", + "log", + "rustc-hash 2.1.2", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.8.10", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.10", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rowan" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" +dependencies = [ + "countme", + "hashbrown 0.14.5", + "rustc-hash 1.1.0", + "text-size", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap 2.13.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structmeta" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] + +[[package]] +name = "structmeta-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tlsh-fixed" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f762ca8308eda1e38512dc88a99f021e5214699ba133de157f588c8bfd0745c7" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "walrus" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6481311b98508f4bc2d0abbfa5d42172e7a54b4b24d8f15e28b0dc650be0c59f" +dependencies = [ + "anyhow", + "gimli 0.26.2", + "id-arena", + "leb128", + "log", + "walrus-macro", + "wasm-encoder 0.214.0", + "wasmparser 0.214.0", +] + +[[package]] +name = "walrus-macro" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ad39ff894c43c9649fa724cdde9a6fc50b855d517ef071a93e5df82fe51d3" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.214.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff694f02a8d7a50b6922b197ae03883fbf18cdb2ae9fbee7b6148456f5f44041" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.221.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5" +dependencies = [ + "leb128", + "wasmparser 0.221.3", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasmparser" +version = "0.214.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5309c1090e3e84dad0d382f42064e9933fdaedb87e468cc239f0eabea73ddcb6" +dependencies = [ + "ahash", + "bitflags", + "hashbrown 0.14.5", + "indexmap 2.13.0", + "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.221.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "wasmprinter" +version = "0.221.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.221.3", +] + +[[package]] +name = "wasmtime" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11976a250672556d1c4c04c6d5d7656ac9192ac9edc42a4587d6c21460010e69" +dependencies = [ + "anyhow", + "bitflags", + "bumpalo", + "cc", + "cfg-if", + "hashbrown 0.14.5", + "indexmap 2.13.0", + "libc", + "log", + "mach2", + "memfd", + "object 0.36.7", + "once_cell", + "paste", + "postcard", + "psm", + "pulley-interpreter", + "rustix 0.38.44", + "serde", + "serde_derive", + "smallvec", + "sptr", + "target-lexicon", + "wasmparser 0.221.3", + "wasmtime-asm-macros", + "wasmtime-component-macro", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-icache-coherence", + "wasmtime-math", + "wasmtime-slab", + "wasmtime-versioned-export-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f178b0d125201fbe9f75beaf849bd3e511891f9e45ba216a5b620802ccf64f2" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-component-macro" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d74de6592ed945d0a602f71243982a304d5d02f1e501b638addf57f42d57dfaf" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser 0.221.3", +] + +[[package]] +name = "wasmtime-component-util" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707dc7b3c112ab5a366b30cfe2fb5b2f8e6a0f682f16df96a5ec582bfe6f056e" + +[[package]] +name = "wasmtime-cranelift" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366be722674d4bf153290fbcbc4d7d16895cc82fb3e869f8d550ff768f9e9e87" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli 0.31.1", + "itertools 0.12.1", + "log", + "object 0.36.7", + "smallvec", + "target-lexicon", + "thiserror 1.0.69", + "wasmparser 0.221.3", + "wasmtime-environ", + "wasmtime-versioned-export-macros", +] + +[[package]] +name = "wasmtime-environ" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdadc1af7097347aa276a4f008929810f726b5b46946971c660b6d421e9994ad" +dependencies = [ + "anyhow", + "cranelift-bitset", + "cranelift-entity", + "gimli 0.31.1", + "indexmap 2.13.0", + "log", + "object 0.36.7", + "postcard", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasm-encoder 0.221.3", + "wasmparser 0.221.3", + "wasmprinter", +] + +[[package]] +name = "wasmtime-fiber" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccba90d4119f081bca91190485650730a617be1fff5228f8c4757ce133d21117" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "rustix 0.38.44", + "wasmtime-asm-macros", + "wasmtime-versioned-export-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5e8552e01692e6c2e5293171704fed8abdec79d1a6995a0870ab190e5747d1" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-math" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29210ec2aa25e00f4d54605cedaf080f39ec01a872c5bd520ad04c67af1dde17" +dependencies = [ + "libm", +] + +[[package]] +name = "wasmtime-slab" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb5821a96fa04ac14bc7b158bb3d5cd7729a053db5a74dad396cd513a5e5ccf" + +[[package]] +name = "wasmtime-versioned-export-macros" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ff86db216dc0240462de40c8290887a613dddf9685508eb39479037ba97b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "wit-parser 0.221.3", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.221.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.221.3", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yara-x" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f9d8d9eb43b0428341ab312b682bf3d740f49bb04706904f8fe0bd1e8fcedbf" +dependencies = [ + "aho-corasick", + "annotate-snippets", + "anyhow", + "array-bytes", + "ascii_tree", + "base64", + "bincode", + "bitflags", + "bitvec", + "bstr", + "const-oid", + "crc32fast", + "der-parser", + "digest", + "dsa", + "ecdsa", + "fmmap", + "globwalk", + "indexmap 2.13.0", + "intaglio", + "itertools 0.14.0", + "lazy_static", + "linkme", + "md-5", + "md2", + "memchr", + "memx", + "nom", + "num-derive", + "num-traits", + "p256", + "p384", + "protobuf", + "protobuf-codegen", + "protobuf-parse", + "regex", + "regex-automata", + "regex-syntax 0.8.10", + "roxmltree", + "rsa", + "rustc-hash 2.1.2", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "strum_macros", + "thiserror 2.0.18", + "tlsh-fixed", + "uuid", + "walrus", + "wasmtime", + "x509-parser", + "yansi", + "yara-x-macros", + "yara-x-parser", + "yara-x-proto", +] + +[[package]] +name = "yara-x-macros" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac0ab7818482696946888a759ce6e7751bfb172b624a6954577406fbb8afe7a8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "yara-x-parser" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e707749cd396e129c92743655c84f793410d64e32628f4a4fc2369cbb70703" +dependencies = [ + "ascii_tree", + "bitflags", + "bstr", + "indexmap 2.13.0", + "itertools 0.14.0", + "logos", + "num-traits", + "rowan", + "rustc-hash 2.1.2", + "serde", + "yansi", +] + +[[package]] +name = "yara-x-proto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4562fd8511916a13269d453f45d5df8e6dd8bedf3d569f76e6e51d22a065b956" +dependencies = [ + "protobuf", + "protobuf-codegen", + "protobuf-parse", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.toml b/PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.toml new file mode 100644 index 0000000..115c6b7 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/Cargo.toml @@ -0,0 +1,20 @@ +# ©AngelaMos | 2026 +# Cargo.toml + +[workspace] +members = ["crates/axumortem", "crates/axumortem-engine"] +resolver = "3" + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT" + +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "2" +anyhow = "1" +tracing = "0.1" +tokio = { version = "1", features = ["full"] } +sha2 = "0.10" diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/Cargo.toml b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/Cargo.toml new file mode 100644 index 0000000..3e0df11 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/Cargo.toml @@ -0,0 +1,26 @@ +# ©AngelaMos | 2026 +# Cargo.toml + +[package] +name = "axumortem-engine" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +goblin = "0.9" +iced-x86 = { version = "1.21", features = [ + "decoder", + "intel", + "nasm", + "instr_info", +] } +yara-x = "0.13" +petgraph = "0.7" +memmap2 = "0.9" +rangemap = "1.5" +sha2 = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/context.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/context.rs new file mode 100644 index 0000000..23008dd --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/context.rs @@ -0,0 +1,66 @@ +// ©AngelaMos | 2026 +// context.rs + +use std::sync::Arc; + +use memmap2::Mmap; + +use crate::formats::FormatResult; +use crate::passes::disasm::DisassemblyResult; +use crate::passes::entropy::EntropyResult; +use crate::passes::imports::ImportResult; +use crate::passes::strings::StringResult; +use crate::passes::threat::ThreatResult; + +pub enum BinarySource { + Mapped(Mmap), + Buffered(Arc<[u8]>), +} + +impl AsRef<[u8]> for BinarySource { + fn as_ref(&self) -> &[u8] { + match self { + Self::Mapped(mmap) => mmap, + Self::Buffered(buf) => buf, + } + } +} + +pub struct AnalysisContext { + source: BinarySource, + pub sha256: String, + pub file_name: String, + pub file_size: u64, + pub format_result: Option, + pub import_result: Option, + pub string_result: Option, + pub entropy_result: Option, + pub disassembly_result: Option, + pub threat_result: Option, +} + +impl AnalysisContext { + pub fn new( + source: BinarySource, + sha256: String, + file_name: String, + file_size: u64, + ) -> Self { + Self { + source, + sha256, + file_name, + file_size, + format_result: None, + import_result: None, + string_result: None, + entropy_result: None, + disassembly_result: None, + threat_result: None, + } + } + + pub fn data(&self) -> &[u8] { + self.source.as_ref() + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/error.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/error.rs new file mode 100644 index 0000000..4c0e368 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/error.rs @@ -0,0 +1,33 @@ +// ©AngelaMos | 2026 +// error.rs + +#[derive(thiserror::Error, Debug)] +pub enum EngineError { + #[error("invalid binary: {reason}")] + InvalidBinary { reason: String }, + + #[error("unsupported format: {format}")] + UnsupportedFormat { format: String }, + + #[error("unsupported architecture: {arch}")] + UnsupportedArchitecture { arch: String }, + + #[error("pass '{pass}' missing dependency: {dependency}")] + MissingDependency { + pass: String, + dependency: String, + }, + + #[error("pass '{pass}' failed")] + PassFailed { + pass: &'static str, + #[source] + source: Box, + }, + + #[error("yara error: {0}")] + Yara(String), + + #[error(transparent)] + Io(#[from] std::io::Error), +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/elf.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/elf.rs new file mode 100644 index 0000000..7a2fe6c --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/elf.rs @@ -0,0 +1,313 @@ +// ©AngelaMos | 2026 +// elf.rs + +use goblin::elf::dynamic::DT_BIND_NOW; +use goblin::elf::header::{ + EM_386, EM_AARCH64, EM_ARM, EM_X86_64, ET_CORE, ET_DYN, + ET_EXEC, ET_REL, +}; +use goblin::elf::program_header::{ + PF_R, PF_W, PF_X, PT_DYNAMIC, PT_GNU_EH_FRAME, + PT_GNU_RELRO, PT_GNU_STACK, PT_INTERP, PT_LOAD, PT_NOTE, + PT_NULL, PT_PHDR, +}; +use goblin::elf::section_header::{ + SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE, SHT_NOBITS, + SHT_SYMTAB, +}; +use goblin::elf::sym::STT_FUNC; +use goblin::elf::Elf; + +use super::{ + detect_common_anomalies, compute_section_hash, ElfInfo, + FormatResult, SectionInfo, SegmentInfo, +}; +use crate::error::EngineError; +use crate::types::{ + Architecture, BinaryFormat, Endianness, SectionPermissions, +}; + +const EI_OSABI: usize = 7; +const ELFOSABI_NONE: u8 = 0; +const ELFOSABI_HPUX: u8 = 1; +const ELFOSABI_NETBSD: u8 = 2; +const ELFOSABI_GNU: u8 = 3; +const ELFOSABI_SOLARIS: u8 = 6; +const ELFOSABI_FREEBSD: u8 = 9; +const ELFOSABI_OPENBSD: u8 = 12; +const ELFOSABI_ARM: u8 = 97; +const ELFOSABI_STANDALONE: u8 = 255; + +const DT_FLAGS: u64 = 30; +const DF_BIND_NOW: u64 = 0x8; + +pub fn parse_elf( + elf: &Elf, + data: &[u8], +) -> Result { + let architecture = + map_architecture(elf.header.e_machine); + let bits = if elf.is_64 { 64 } else { 32 }; + let endianness = if elf.little_endian { + Endianness::Little + } else { + Endianness::Big + }; + let entry_point = elf.header.e_entry; + + let has_symtab = elf + .section_headers + .iter() + .any(|sh| sh.sh_type == SHT_SYMTAB); + let is_stripped = !has_symtab; + + let has_interp = elf + .program_headers + .iter() + .any(|ph| ph.p_type == PT_INTERP); + let is_pie = + elf.header.e_type == ET_DYN && has_interp; + + let has_debug_info = + elf.section_headers.iter().any(|sh| { + elf.shdr_strtab + .get_at(sh.sh_name) + .is_some_and(|name| { + name.starts_with(".debug_") + }) + }); + + let sections = build_sections(elf, data); + let segments = build_segments(elf); + let anomalies = detect_common_anomalies( + §ions, + entry_point, + is_stripped, + ); + let elf_info = build_elf_info(elf); + let function_hints = + collect_function_hints(elf, entry_point); + + Ok(FormatResult { + format: BinaryFormat::Elf, + architecture, + bits, + endianness, + entry_point, + is_stripped, + is_pie, + has_debug_info, + sections, + segments, + anomalies, + pe_info: None, + elf_info: Some(elf_info), + macho_info: None, + function_hints, + }) +} + +fn map_architecture(machine: u16) -> Architecture { + match machine { + EM_386 => Architecture::X86, + EM_X86_64 => Architecture::X86_64, + EM_ARM => Architecture::Arm, + EM_AARCH64 => Architecture::Aarch64, + other => { + Architecture::Other(format!( + "elf-machine-{other}" + )) + } + } +} + +fn os_abi_name(abi: u8) -> String { + match abi { + ELFOSABI_NONE => "SysV".into(), + ELFOSABI_HPUX => "HP-UX".into(), + ELFOSABI_NETBSD => "NetBSD".into(), + ELFOSABI_GNU => "GNU/Linux".into(), + ELFOSABI_SOLARIS => "Solaris".into(), + ELFOSABI_FREEBSD => "FreeBSD".into(), + ELFOSABI_OPENBSD => "OpenBSD".into(), + ELFOSABI_ARM => "ARM".into(), + ELFOSABI_STANDALONE => "Standalone".into(), + other => format!("Unknown({other})"), + } +} + +fn elf_type_name(e_type: u16) -> String { + match e_type { + ET_REL => "REL".into(), + ET_EXEC => "EXEC".into(), + ET_DYN => "DYN".into(), + ET_CORE => "CORE".into(), + other => format!("Unknown({other})"), + } +} + +fn segment_type_name(p_type: u32) -> Option { + Some( + match p_type { + PT_NULL => "NULL", + PT_LOAD => "LOAD", + PT_DYNAMIC => "DYNAMIC", + PT_INTERP => "INTERP", + PT_NOTE => "NOTE", + PT_PHDR => "PHDR", + PT_GNU_EH_FRAME => "GNU_EH_FRAME", + PT_GNU_STACK => "GNU_STACK", + PT_GNU_RELRO => "GNU_RELRO", + _ => return Some(format!("0x{p_type:x}")), + } + .into(), + ) +} + +fn build_sections( + elf: &Elf, + data: &[u8], +) -> Vec { + elf.section_headers + .iter() + .skip(1) + .map(|shdr| { + let name = elf + .shdr_strtab + .get_at(shdr.sh_name) + .unwrap_or("") + .to_string(); + + let is_nobits = shdr.sh_type == SHT_NOBITS; + let raw_offset = if is_nobits { + 0 + } else { + shdr.sh_offset + }; + let raw_size = if is_nobits { + 0 + } else { + shdr.sh_size + }; + + let permissions = SectionPermissions { + read: (shdr.sh_flags + & u64::from(SHF_ALLOC)) + != 0, + write: (shdr.sh_flags + & u64::from(SHF_WRITE)) + != 0, + execute: (shdr.sh_flags + & u64::from(SHF_EXECINSTR)) + != 0, + }; + + let sha256 = compute_section_hash( + data, raw_offset, raw_size, + ); + + SectionInfo { + name, + virtual_address: shdr.sh_addr, + virtual_size: shdr.sh_size, + raw_offset, + raw_size, + permissions, + sha256, + } + }) + .collect() +} + +fn build_segments(elf: &Elf) -> Vec { + elf.program_headers + .iter() + .map(|phdr| { + let name = segment_type_name(phdr.p_type); + let permissions = SectionPermissions { + read: (phdr.p_flags & PF_R) != 0, + write: (phdr.p_flags & PF_W) != 0, + execute: (phdr.p_flags & PF_X) != 0, + }; + + SegmentInfo { + name, + virtual_address: phdr.p_vaddr, + virtual_size: phdr.p_memsz, + file_offset: phdr.p_offset, + file_size: phdr.p_filesz, + permissions, + } + }) + .collect() +} + +fn build_elf_info(elf: &Elf) -> ElfInfo { + let os_abi = + os_abi_name(elf.header.e_ident[EI_OSABI]); + let elf_type = elf_type_name(elf.header.e_type); + let interpreter = + elf.interpreter.map(|s| s.to_string()); + + let gnu_relro = elf + .program_headers + .iter() + .any(|ph| ph.p_type == PT_GNU_RELRO); + + let stack_executable = elf + .program_headers + .iter() + .find(|ph| ph.p_type == PT_GNU_STACK) + .is_some_and(|ph| (ph.p_flags & PF_X) != 0); + + let mut bind_now = false; + if let Some(dynamic) = &elf.dynamic { + for dyn_entry in &dynamic.dyns { + let tag = dyn_entry.d_tag as u64; + if tag == DT_BIND_NOW { + bind_now = true; + } + if tag == DT_FLAGS + && (dyn_entry.d_val & DF_BIND_NOW) != 0 + { + bind_now = true; + } + } + } + + let needed_libraries = elf + .libraries + .iter() + .map(|s| s.to_string()) + .collect(); + + ElfInfo { + os_abi, + elf_type, + interpreter, + gnu_relro, + bind_now, + stack_executable, + needed_libraries, + } +} + +fn collect_function_hints( + elf: &Elf, + entry_point: u64, +) -> Vec { + let mut hints: Vec = elf + .syms + .iter() + .chain(elf.dynsyms.iter()) + .filter(|sym| { + sym.st_type() == STT_FUNC + && sym.st_value != 0 + && sym.st_value != entry_point + }) + .map(|sym| sym.st_value) + .collect(); + hints.sort_unstable(); + hints.dedup(); + hints +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/macho.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/macho.rs new file mode 100644 index 0000000..59a33e1 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/macho.rs @@ -0,0 +1,361 @@ +// ©AngelaMos | 2026 +// macho.rs + +use goblin::mach::cputype::{ + CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_X86, + CPU_TYPE_X86_64, +}; +use goblin::mach::load_command::CommandVariant; +use goblin::mach::{Mach, MachO}; + +use super::{ + compute_section_hash, detect_common_anomalies, + FormatResult, MachOInfo, SectionInfo, SegmentInfo, +}; +use crate::error::EngineError; +use crate::types::{ + Architecture, BinaryFormat, Endianness, SectionPermissions, +}; + +const MH_OBJECT: u32 = 1; +const MH_EXECUTE: u32 = 2; +const MH_DYLIB: u32 = 6; +const MH_BUNDLE: u32 = 8; +const MH_DSYM: u32 = 10; +const MH_KEXT_BUNDLE: u32 = 11; + +const VM_PROT_READ: u32 = 0x01; +const VM_PROT_WRITE: u32 = 0x02; +const VM_PROT_EXECUTE: u32 = 0x04; + +pub fn parse_macho( + mach: &Mach, + data: &[u8], +) -> Result { + match mach { + Mach::Binary(macho) => { + parse_single_macho(macho, data, false) + } + Mach::Fat(fat) => { + for arch in fat.iter_arches() { + let arch = arch.map_err(|e| { + EngineError::InvalidBinary { + reason: e.to_string(), + } + })?; + let offset = arch.offset as usize; + let size = arch.size as usize; + let end = offset.saturating_add(size); + if end <= data.len() { + let macho = MachO::parse(data, offset) + .map_err(|e| { + EngineError::InvalidBinary { + reason: e.to_string(), + } + })?; + return parse_single_macho( + &macho, data, true, + ); + } + } + Err(EngineError::InvalidBinary { + reason: "no valid architecture in \ + universal binary" + .into(), + }) + } + } +} + +fn parse_single_macho( + macho: &MachO, + data: &[u8], + is_universal: bool, +) -> Result { + let architecture = + map_architecture(macho.header.cputype); + let bits = if macho.is_64 { 64 } else { 32 }; + let endianness = if macho.little_endian { + Endianness::Little + } else { + Endianness::Big + }; + let entry_point = macho.entry; + + let symbols: Vec<_> = + macho.symbols().flatten().collect(); + let is_stripped = symbols.is_empty(); + + let has_debug_info = macho.segments.iter().any(|seg| { + seg.name().is_ok_and(|n| n == "__DWARF") + }); + + let is_pie = macho.header.flags & 0x0020_0000 != 0; + + let sections = build_sections(macho, data); + let segments = build_segments(macho); + let anomalies = detect_common_anomalies( + §ions, + entry_point, + is_stripped, + ); + let macho_info = + build_macho_info(macho, is_universal); + + let function_hints: Vec = macho + .symbols() + .flatten() + .filter(|(_, nlist)| { + !nlist.is_stab() + && nlist.n_type & 0x0e == 0x0e + && nlist.n_value != 0 + && nlist.n_value != entry_point + }) + .map(|(_, nlist)| nlist.n_value) + .collect(); + + Ok(FormatResult { + format: BinaryFormat::MachO, + architecture, + bits, + endianness, + entry_point, + is_stripped, + is_pie, + has_debug_info, + sections, + segments, + anomalies, + pe_info: None, + elf_info: None, + macho_info: Some(macho_info), + function_hints, + }) +} + +fn map_architecture(cputype: u32) -> Architecture { + match cputype { + CPU_TYPE_X86 => Architecture::X86, + CPU_TYPE_X86_64 => Architecture::X86_64, + CPU_TYPE_ARM => Architecture::Arm, + CPU_TYPE_ARM64 => Architecture::Aarch64, + other => { + Architecture::Other(format!( + "mach-cpu-{other:#x}" + )) + } + } +} + +fn file_type_name(filetype: u32) -> String { + match filetype { + MH_OBJECT => "Object".into(), + MH_EXECUTE => "Execute".into(), + MH_DYLIB => "Dylib".into(), + MH_BUNDLE => "Bundle".into(), + MH_DSYM => "Dsym".into(), + MH_KEXT_BUNDLE => "Kext".into(), + other => format!("Unknown({other})"), + } +} + +fn cpu_subtype_name( + cputype: u32, + cpusubtype: u32, +) -> String { + let subtype = cpusubtype & 0x00FF_FFFF; + match cputype { + CPU_TYPE_X86 | CPU_TYPE_X86_64 => { + match subtype { + 3 => "ALL".into(), + 4 => "486".into(), + 8 => "PENTIUM_3".into(), + 9 => "PENTIUM_M".into(), + 10 => "PENTIUM_4".into(), + 11 => "ITANIUM".into(), + 12 => "XEON".into(), + _ => format!("{subtype}"), + } + } + CPU_TYPE_ARM => match subtype { + 6 => "v6".into(), + 9 => "v7".into(), + 11 => "v7f".into(), + 12 => "v7s".into(), + 13 => "v7k".into(), + _ => format!("{subtype}"), + }, + CPU_TYPE_ARM64 => match subtype { + 0 => "ALL".into(), + 1 => "v8".into(), + 2 => "E".into(), + _ => format!("{subtype}"), + }, + _ => format!("{subtype}"), + } +} + +fn build_sections( + macho: &MachO, + data: &[u8], +) -> Vec { + let mut sections = Vec::new(); + for segment in macho.segments.iter() { + let initprot = segment.initprot; + let seg_permissions = SectionPermissions { + read: (initprot & VM_PROT_READ) != 0, + write: (initprot & VM_PROT_WRITE) != 0, + execute: (initprot & VM_PROT_EXECUTE) + != 0, + }; + for section_result in segment.into_iter() { + let Ok((section, _section_data)) = + section_result + else { + continue; + }; + let name = section + .name() + .unwrap_or("???") + .to_string(); + let raw_offset = section.offset as u64; + let raw_size = section.size; + let sha256 = compute_section_hash( + data, raw_offset, raw_size, + ); + + sections.push(SectionInfo { + name, + virtual_address: section.addr, + virtual_size: section.size, + raw_offset, + raw_size, + permissions: seg_permissions.clone(), + sha256, + }); + } + } + sections +} + +fn build_segments(macho: &MachO) -> Vec { + macho + .segments + .iter() + .map(|seg| { + let name = seg + .name() + .ok() + .map(|n| n.to_string()); + let initprot = seg.initprot; + let permissions = SectionPermissions { + read: (initprot & VM_PROT_READ) != 0, + write: (initprot & VM_PROT_WRITE) != 0, + execute: (initprot & VM_PROT_EXECUTE) + != 0, + }; + + SegmentInfo { + name, + virtual_address: seg.vmaddr, + virtual_size: seg.vmsize, + file_offset: seg.fileoff, + file_size: seg.filesize, + permissions, + } + }) + .collect() +} + +fn build_macho_info( + macho: &MachO, + is_universal: bool, +) -> MachOInfo { + let file_type = + file_type_name(macho.header.filetype); + let cpu_subtype = cpu_subtype_name( + macho.header.cputype, + macho.header.cpusubtype, + ); + + let mut has_code_signature = false; + let mut has_function_starts = false; + let mut min_os_version: Option = None; + let mut sdk_version: Option = None; + + for lc in &macho.load_commands { + match &lc.command { + CommandVariant::CodeSignature(_) => { + has_code_signature = true; + } + CommandVariant::FunctionStarts(_) => { + has_function_starts = true; + } + CommandVariant::VersionMinMacosx(ver) => { + min_os_version = Some(format!( + "{}.{}.{}", + ver.version >> 16, + (ver.version >> 8) & 0xFF, + ver.version & 0xFF, + )); + sdk_version = Some(format!( + "{}.{}.{}", + ver.sdk >> 16, + (ver.sdk >> 8) & 0xFF, + ver.sdk & 0xFF, + )); + } + CommandVariant::VersionMinIphoneos(ver) => { + if min_os_version.is_none() { + min_os_version = Some(format!( + "iOS {}.{}.{}", + ver.version >> 16, + (ver.version >> 8) & 0xFF, + ver.version & 0xFF, + )); + sdk_version = Some(format!( + "{}.{}.{}", + ver.sdk >> 16, + (ver.sdk >> 8) & 0xFF, + ver.sdk & 0xFF, + )); + } + } + CommandVariant::BuildVersion(bv) => { + if min_os_version.is_none() { + min_os_version = Some(format!( + "{}.{}.{}", + bv.minos >> 16, + (bv.minos >> 8) & 0xFF, + bv.minos & 0xFF, + )); + sdk_version = Some(format!( + "{}.{}.{}", + bv.sdk >> 16, + (bv.sdk >> 8) & 0xFF, + bv.sdk & 0xFF, + )); + } + } + _ => {} + } + } + + let dylibs = macho + .libs + .iter() + .filter(|lib| !lib.is_empty()) + .map(|lib| lib.to_string()) + .collect(); + + MachOInfo { + file_type, + cpu_subtype, + is_universal, + has_code_signature, + min_os_version, + sdk_version, + dylibs, + has_function_starts, + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/mod.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/mod.rs new file mode 100644 index 0000000..a31a65f --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/mod.rs @@ -0,0 +1,335 @@ +// ©AngelaMos | 2026 +// mod.rs + +mod elf; +mod macho; +mod pe; + +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::error::EngineError; +use crate::types::{ + Architecture, BinaryFormat, Endianness, SectionPermissions, +}; + +pub const SUSPICIOUS_SECTION_NAMES: &[(&str, &str)] = &[ + ("UPX0", "UPX packer"), + ("UPX1", "UPX packer"), + ("UPX2", "UPX packer"), + (".nsp0", "NSPack"), + (".nsp1", "NSPack"), + (".nsp2", "NSPack"), + (".aspack", "ASPack"), + (".adata", "ASPack"), + (".MPress1", "MPress"), + (".MPress2", "MPress"), + (".themida", "Themida"), + (".vmp0", "VMProtect"), + (".vmp1", "VMProtect"), + (".enigma1", "Enigma"), + (".enigma2", "Enigma"), +]; + +const VIRTUAL_RAW_RATIO_THRESHOLD: f64 = 10.0; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FormatResult { + pub format: BinaryFormat, + pub architecture: Architecture, + pub bits: u8, + pub endianness: Endianness, + pub entry_point: u64, + pub is_stripped: bool, + pub is_pie: bool, + pub has_debug_info: bool, + pub sections: Vec, + pub segments: Vec, + pub anomalies: Vec, + pub pe_info: Option, + pub elf_info: Option, + pub macho_info: Option, + #[serde(default)] + pub function_hints: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SectionInfo { + pub name: String, + pub virtual_address: u64, + pub virtual_size: u64, + pub raw_offset: u64, + pub raw_size: u64, + pub permissions: SectionPermissions, + pub sha256: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SegmentInfo { + pub name: Option, + pub virtual_address: u64, + pub virtual_size: u64, + pub file_offset: u64, + pub file_size: u64, + pub permissions: SectionPermissions, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum FormatAnomaly { + EntryPointOutsideText { + ep: u64, + text_range: (u64, u64), + }, + EntryPointInLastSection { + ep: u64, + section: String, + }, + EntryPointOutsideSections { + ep: u64, + }, + RwxSection { + name: String, + }, + EmptySectionName { + index: usize, + }, + StrippedBinary, + SuspiciousSectionName { + name: String, + reason: String, + }, + ZeroSizeCodeSection { + name: String, + }, + VirtualRawSizeMismatch { + name: String, + virtual_size: u64, + raw_size: u64, + ratio: f64, + }, + OverlayData { + offset: u64, + size: u64, + }, + TlsCallbacksPresent { + count: usize, + }, + NoImportTable, + SuspiciousTimestamp { + value: u32, + reason: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PeInfo { + pub image_base: u64, + pub subsystem: String, + pub dll_characteristics: PeDllCharacteristics, + pub timestamp: u32, + pub linker_version: String, + pub tls_callback_count: usize, + pub has_overlay: bool, + pub overlay_size: u64, + pub rich_header_present: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PeDllCharacteristics { + pub aslr: bool, + pub dep: bool, + pub cfg: bool, + pub no_seh: bool, + pub force_integrity: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ElfInfo { + pub os_abi: String, + pub elf_type: String, + pub interpreter: Option, + pub gnu_relro: bool, + pub bind_now: bool, + pub stack_executable: bool, + pub needed_libraries: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MachOInfo { + pub file_type: String, + pub cpu_subtype: String, + pub is_universal: bool, + pub has_code_signature: bool, + pub min_os_version: Option, + pub sdk_version: Option, + pub dylibs: Vec, + pub has_function_starts: bool, +} + +pub fn parse_format( + data: &[u8], +) -> Result { + let object = + goblin::Object::parse(data).map_err(|e| { + EngineError::InvalidBinary { + reason: e.to_string(), + } + })?; + + match &object { + goblin::Object::Elf(elf_obj) => { + elf::parse_elf(elf_obj, data) + } + goblin::Object::PE(pe_obj) => { + pe::parse_pe(pe_obj, data) + } + goblin::Object::Mach(mach_obj) => { + macho::parse_macho(mach_obj, data) + } + _ => Err(EngineError::UnsupportedFormat { + format: "unknown".into(), + }), + } +} + +fn compute_section_hash( + data: &[u8], + offset: u64, + size: u64, +) -> String { + if size == 0 { + return String::new(); + } + let start = offset as usize; + let end = start.saturating_add(size as usize); + if start >= data.len() || end > data.len() { + return String::new(); + } + let hash = Sha256::digest(&data[start..end]); + format!("{hash:x}") +} + +fn check_suspicious_name(name: &str) -> Option { + for &(suspicious, reason) in SUSPICIOUS_SECTION_NAMES { + if name == suspicious { + return Some(reason.into()); + } + } + None +} + +fn detect_common_anomalies( + sections: &[SectionInfo], + entry_point: u64, + is_stripped: bool, +) -> Vec { + let mut anomalies = Vec::new(); + + let text_section = + sections.iter().find(|s| s.name == ".text"); + if let Some(text) = text_section { + let text_end = + text.virtual_address + text.virtual_size; + if entry_point != 0 + && (entry_point < text.virtual_address + || entry_point >= text_end) + { + anomalies.push( + FormatAnomaly::EntryPointOutsideText { + ep: entry_point, + text_range: ( + text.virtual_address, + text_end, + ), + }, + ); + } + } + + if let Some(last) = sections.last() { + let last_end = + last.virtual_address + last.virtual_size; + if entry_point >= last.virtual_address + && entry_point < last_end + { + anomalies.push( + FormatAnomaly::EntryPointInLastSection { + ep: entry_point, + section: last.name.clone(), + }, + ); + } + } + + let ep_in_any = sections.iter().any(|s| { + entry_point >= s.virtual_address + && entry_point + < s.virtual_address + s.virtual_size + }); + if !ep_in_any && entry_point != 0 { + anomalies.push( + FormatAnomaly::EntryPointOutsideSections { + ep: entry_point, + }, + ); + } + + for (idx, section) in sections.iter().enumerate() { + if section.permissions.is_rwx() { + anomalies.push(FormatAnomaly::RwxSection { + name: section.name.clone(), + }); + } + + if section.name.is_empty() { + anomalies.push( + FormatAnomaly::EmptySectionName { + index: idx, + }, + ); + } + + if let Some(reason) = + check_suspicious_name(§ion.name) + { + anomalies.push( + FormatAnomaly::SuspiciousSectionName { + name: section.name.clone(), + reason, + }, + ); + } + + if section.permissions.execute + && section.virtual_size == 0 + { + anomalies.push( + FormatAnomaly::ZeroSizeCodeSection { + name: section.name.clone(), + }, + ); + } + + if section.raw_size > 0 { + let ratio = section.virtual_size as f64 + / section.raw_size as f64; + if ratio > VIRTUAL_RAW_RATIO_THRESHOLD { + anomalies.push( + FormatAnomaly::VirtualRawSizeMismatch { + name: section.name.clone(), + virtual_size: section + .virtual_size, + raw_size: section.raw_size, + ratio, + }, + ); + } + } + } + + if is_stripped { + anomalies.push(FormatAnomaly::StrippedBinary); + } + + anomalies +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/pe.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/pe.rs new file mode 100644 index 0000000..67d2c28 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/formats/pe.rs @@ -0,0 +1,332 @@ +// ©AngelaMos | 2026 +// pe.rs + +use goblin::pe::PE; + +use super::{ + compute_section_hash, detect_common_anomalies, + FormatAnomaly, FormatResult, PeDllCharacteristics, PeInfo, + SectionInfo, SegmentInfo, +}; +use crate::error::EngineError; +use crate::types::{ + Architecture, BinaryFormat, Endianness, SectionPermissions, +}; + +const COFF_MACHINE_I386: u16 = 0x14c; +const COFF_MACHINE_AMD64: u16 = 0x8664; +const COFF_MACHINE_ARM: u16 = 0x1c0; +const COFF_MACHINE_ARMNT: u16 = 0x1c4; +const COFF_MACHINE_ARM64: u16 = 0xaa64; + +const IMAGE_SCN_MEM_READ: u32 = 0x4000_0000; +const IMAGE_SCN_MEM_WRITE: u32 = 0x8000_0000; +const IMAGE_SCN_MEM_EXECUTE: u32 = 0x2000_0000; + +const IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE: u16 = 0x0040; +const IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY: u16 = + 0x0080; +const IMAGE_DLLCHARACTERISTICS_NX_COMPAT: u16 = 0x0100; +const IMAGE_DLLCHARACTERISTICS_NO_SEH: u16 = 0x0400; +const IMAGE_DLLCHARACTERISTICS_GUARD_CF: u16 = 0x4000; + +const IMAGE_SUBSYSTEM_UNKNOWN: u16 = 0; +const IMAGE_SUBSYSTEM_NATIVE: u16 = 1; +const IMAGE_SUBSYSTEM_WINDOWS_GUI: u16 = 2; +const IMAGE_SUBSYSTEM_WINDOWS_CUI: u16 = 3; +const IMAGE_SUBSYSTEM_POSIX_CUI: u16 = 7; +const IMAGE_SUBSYSTEM_WINDOWS_CE_GUI: u16 = 9; +const IMAGE_SUBSYSTEM_EFI_APPLICATION: u16 = 10; +const IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: u16 = 11; +const IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: u16 = 12; +const IMAGE_SUBSYSTEM_XBOX: u16 = 14; + +const PE_TIMESTAMP_MIN_VALID: u32 = 631_152_000; +const PE_TIMESTAMP_MAX_VALID: u32 = 4_102_444_800; + +const RICH_SIGNATURE: &[u8] = b"Rich"; + +pub fn parse_pe( + pe: &PE, + data: &[u8], +) -> Result { + let architecture = map_architecture( + pe.header.coff_header.machine, + ); + let bits = if pe.is_64 { 64 } else { 32 }; + let endianness = Endianness::Little; + let entry_point = pe.entry as u64; + + let is_stripped = pe.debug_data.is_none(); + let optional = pe.header.optional_header.as_ref(); + let dll_chars = optional.map_or(0, |oh| { + oh.windows_fields.dll_characteristics + }); + let is_pie = (dll_chars + & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) + != 0; + let has_debug_info = pe.debug_data.is_some(); + + let sections = build_sections(pe, data); + let segments = build_segments(pe); + + let timestamp = + pe.header.coff_header.time_date_stamp; + let image_base = optional + .map_or(0, |oh| oh.windows_fields.image_base); + let subsystem_raw = optional + .map_or(0, |oh| oh.windows_fields.subsystem); + let linker_version = optional.map_or_else( + || "0.0".into(), + |oh| { + format!( + "{}.{}", + oh.standard_fields.major_linker_version, + oh.standard_fields.minor_linker_version, + ) + }, + ); + + let has_tls = pe.tls_data.is_some(); + let tls_callback_count = usize::from(has_tls); + + let max_section_end = pe + .sections + .iter() + .map(|s| { + s.pointer_to_raw_data as u64 + + s.size_of_raw_data as u64 + }) + .max() + .unwrap_or(0); + let file_size = data.len() as u64; + let has_overlay = + max_section_end > 0 && max_section_end < file_size; + let overlay_size = if has_overlay { + file_size - max_section_end + } else { + 0 + }; + + let pe_offset = + pe.header.dos_header.pe_pointer as usize; + let rich_header_present = detect_rich_header( + data, + pe_offset, + ); + + let pe_info = PeInfo { + image_base, + subsystem: subsystem_name(subsystem_raw), + dll_characteristics: PeDllCharacteristics { + aslr: (dll_chars + & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) + != 0, + dep: (dll_chars + & IMAGE_DLLCHARACTERISTICS_NX_COMPAT) + != 0, + cfg: (dll_chars + & IMAGE_DLLCHARACTERISTICS_GUARD_CF) + != 0, + no_seh: (dll_chars + & IMAGE_DLLCHARACTERISTICS_NO_SEH) + != 0, + force_integrity: (dll_chars + & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY) + != 0, + }, + timestamp, + linker_version, + tls_callback_count, + has_overlay, + overlay_size, + rich_header_present, + }; + + let mut anomalies = detect_common_anomalies( + §ions, + entry_point, + is_stripped, + ); + detect_pe_anomalies( + &mut anomalies, + pe, + timestamp, + has_tls, + has_overlay, + max_section_end, + file_size, + ); + + let function_hints: Vec = pe + .exports + .iter() + .filter(|e| e.rva != 0) + .map(|e| image_base + e.rva as u64) + .filter(|&addr| addr != entry_point) + .collect(); + + Ok(FormatResult { + format: BinaryFormat::Pe, + architecture, + bits, + endianness, + entry_point, + is_stripped, + is_pie, + has_debug_info, + sections, + segments, + anomalies, + pe_info: Some(pe_info), + elf_info: None, + macho_info: None, + function_hints, + }) +} + +fn map_architecture(machine: u16) -> Architecture { + match machine { + COFF_MACHINE_I386 => Architecture::X86, + COFF_MACHINE_AMD64 => Architecture::X86_64, + COFF_MACHINE_ARM | COFF_MACHINE_ARMNT => { + Architecture::Arm + } + COFF_MACHINE_ARM64 => Architecture::Aarch64, + other => { + Architecture::Other(format!( + "pe-machine-{other:#x}" + )) + } + } +} + +fn subsystem_name(subsystem: u16) -> String { + match subsystem { + IMAGE_SUBSYSTEM_UNKNOWN => "Unknown".into(), + IMAGE_SUBSYSTEM_NATIVE => "Native".into(), + IMAGE_SUBSYSTEM_WINDOWS_GUI => "GUI".into(), + IMAGE_SUBSYSTEM_WINDOWS_CUI => "Console".into(), + IMAGE_SUBSYSTEM_POSIX_CUI => "POSIX".into(), + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI => { + "Windows CE".into() + } + IMAGE_SUBSYSTEM_EFI_APPLICATION => { + "EFI Application".into() + } + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER => { + "EFI Boot Service Driver".into() + } + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER => { + "EFI Runtime Driver".into() + } + IMAGE_SUBSYSTEM_XBOX => "Xbox".into(), + other => format!("Unknown({other})"), + } +} + +fn build_sections( + pe: &PE, + data: &[u8], +) -> Vec { + pe.sections + .iter() + .map(|section| { + let name = section + .name() + .unwrap_or_default() + .to_string(); + let raw_offset = + section.pointer_to_raw_data as u64; + let raw_size = + section.size_of_raw_data as u64; + let chars = section.characteristics; + let permissions = SectionPermissions { + read: (chars & IMAGE_SCN_MEM_READ) != 0, + write: (chars & IMAGE_SCN_MEM_WRITE) != 0, + execute: (chars & IMAGE_SCN_MEM_EXECUTE) + != 0, + }; + let sha256 = compute_section_hash( + data, raw_offset, raw_size, + ); + + SectionInfo { + name, + virtual_address: section.virtual_address + as u64, + virtual_size: section.virtual_size as u64, + raw_offset, + raw_size, + permissions, + sha256, + } + }) + .collect() +} + +fn build_segments(_pe: &PE) -> Vec { + Vec::new() +} + +fn detect_rich_header( + data: &[u8], + pe_offset: usize, +) -> bool { + let end = pe_offset.min(data.len()); + data[..end] + .windows(RICH_SIGNATURE.len()) + .any(|w| w == RICH_SIGNATURE) +} + +fn detect_pe_anomalies( + anomalies: &mut Vec, + pe: &PE, + timestamp: u32, + has_tls: bool, + has_overlay: bool, + overlay_offset: u64, + file_size: u64, +) { + if timestamp == 0 { + anomalies.push( + FormatAnomaly::SuspiciousTimestamp { + value: timestamp, + reason: "zeroed timestamp".into(), + }, + ); + } else if timestamp < PE_TIMESTAMP_MIN_VALID { + anomalies.push( + FormatAnomaly::SuspiciousTimestamp { + value: timestamp, + reason: "timestamp before 1990".into(), + }, + ); + } else if timestamp > PE_TIMESTAMP_MAX_VALID { + anomalies.push( + FormatAnomaly::SuspiciousTimestamp { + value: timestamp, + reason: "timestamp after 2100".into(), + }, + ); + } + + if has_tls { + anomalies.push( + FormatAnomaly::TlsCallbacksPresent { + count: 1, + }, + ); + } + + if pe.imports.is_empty() { + anomalies.push(FormatAnomaly::NoImportTable); + } + + if has_overlay { + anomalies.push(FormatAnomaly::OverlayData { + offset: overlay_offset, + size: file_size - overlay_offset, + }); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/lib.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/lib.rs new file mode 100644 index 0000000..ee0e2f1 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/lib.rs @@ -0,0 +1,76 @@ +// ©AngelaMos | 2026 +// lib.rs + +pub mod context; +pub mod error; +pub mod formats; +pub mod pass; +pub mod passes; +pub mod types; +pub mod yara; + +use std::sync::Arc; + +use sha2::{Digest, Sha256}; + +use context::{AnalysisContext, BinarySource}; +use error::EngineError; +use pass::{PassManager, PassReport}; +use passes::disasm::DisasmPass; +use passes::entropy::EntropyPass; +use passes::format::FormatPass; +use passes::imports::ImportPass; +use passes::strings::StringPass; +use passes::threat::ThreatPass; + +pub struct AnalysisEngine { + pass_manager: PassManager, +} + +impl AnalysisEngine { + pub fn new() -> Result { + let passes: Vec> = + vec![ + Box::new(FormatPass), + Box::new(ImportPass), + Box::new(StringPass), + Box::new(EntropyPass), + Box::new(DisasmPass), + Box::new(ThreatPass), + ]; + + let pass_manager = PassManager::new(passes); + + Ok(Self { pass_manager }) + } + + pub fn analyze( + &self, + data: &[u8], + file_name: &str, + ) -> (AnalysisContext, PassReport) { + let sha256 = compute_sha256(data); + let file_size = data.len() as u64; + let mut ctx = AnalysisContext::new( + BinarySource::Buffered(Arc::from( + data.to_vec(), + )), + sha256, + file_name.to_string(), + file_size, + ); + let report = + self.pass_manager.run_all(&mut ctx); + (ctx, report) + } +} + +pub fn sha256_hex(data: &[u8]) -> String { + compute_sha256(data) +} + +fn compute_sha256(data: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(data); + format!("{:x}", hasher.finalize()) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/pass.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/pass.rs new file mode 100644 index 0000000..98627fb --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/pass.rs @@ -0,0 +1,386 @@ +// ©AngelaMos | 2026 +// pass.rs + +use std::collections::{HashMap, VecDeque}; +use std::time::Instant; + +use crate::context::AnalysisContext; +use crate::error::EngineError; + +mod private { + pub trait Sealed {} +} + +pub trait AnalysisPass: private::Sealed + Send + Sync { + fn name(&self) -> &'static str; + fn dependencies(&self) -> &[&'static str]; + fn run( + &self, + ctx: &mut AnalysisContext, + ) -> Result<(), EngineError>; +} + +#[derive(Debug, Clone)] +pub struct PassOutcome { + pub name: &'static str, + pub success: bool, + pub duration_ms: u64, + pub error_message: Option, +} + +#[derive(Debug, Clone)] +pub struct PassReport { + pub outcomes: Vec, +} + +impl PassReport { + pub fn all_succeeded(&self) -> bool { + self.outcomes.iter().all(|o| o.success) + } + + pub fn failed_passes(&self) -> Vec<&PassOutcome> { + self.outcomes + .iter() + .filter(|o| !o.success) + .collect() + } +} + +pub struct PassManager { + passes: Vec>, + order: Vec, +} + +impl PassManager { + pub fn new( + passes: Vec>, + ) -> Self { + let order = topological_order(&passes); + Self { passes, order } + } + + pub fn run_all( + &self, + ctx: &mut AnalysisContext, + ) -> PassReport { + let mut outcomes = Vec::with_capacity(self.passes.len()); + + for &idx in &self.order { + let pass = &self.passes[idx]; + let start = Instant::now(); + let result = pass.run(ctx); + let duration_ms = + start.elapsed().as_millis() as u64; + + let outcome = match result { + Ok(()) => { + tracing::info!( + pass = pass.name(), + duration_ms, + "pass completed" + ); + PassOutcome { + name: pass.name(), + success: true, + duration_ms, + error_message: None, + } + } + Err(e) => { + tracing::error!( + pass = pass.name(), + error = %e, + duration_ms, + "pass failed" + ); + PassOutcome { + name: pass.name(), + success: false, + duration_ms, + error_message: Some(e.to_string()), + } + } + }; + + outcomes.push(outcome); + } + + PassReport { outcomes } + } +} + +fn topological_order( + passes: &[Box], +) -> Vec { + let name_to_idx: HashMap<&str, usize> = passes + .iter() + .enumerate() + .map(|(i, p)| (p.name(), i)) + .collect(); + + let n = passes.len(); + let mut in_degree = vec![0usize; n]; + let mut adjacency: Vec> = vec![vec![]; n]; + + for (idx, pass) in passes.iter().enumerate() { + for dep_name in pass.dependencies() { + if let Some(&dep_idx) = name_to_idx.get(dep_name) + { + adjacency[dep_idx].push(idx); + in_degree[idx] += 1; + } + } + } + + let mut queue: VecDeque = in_degree + .iter() + .enumerate() + .filter(|&(_, deg)| *deg == 0) + .map(|(i, _)| i) + .collect(); + + let mut order = Vec::with_capacity(n); + + while let Some(node) = queue.pop_front() { + order.push(node); + for &neighbor in &adjacency[node] { + in_degree[neighbor] -= 1; + if in_degree[neighbor] == 0 { + queue.push_back(neighbor); + } + } + } + + assert_eq!( + order.len(), + n, + "cycle detected in pass dependencies — this is a programmer error" + ); + + order +} + +pub(crate) use private::Sealed; + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::{Arc, Mutex}; + + struct MockPass { + name: &'static str, + deps: Vec<&'static str>, + log: Arc>>, + should_fail: bool, + } + + impl Sealed for MockPass {} + + impl AnalysisPass for MockPass { + fn name(&self) -> &'static str { + self.name + } + + fn dependencies(&self) -> &[&'static str] { + &self.deps + } + + fn run( + &self, + _ctx: &mut AnalysisContext, + ) -> Result<(), EngineError> { + self.log.lock().unwrap().push(self.name); + if self.should_fail { + return Err(EngineError::PassFailed { + pass: self.name, + source: "mock failure".into(), + }); + } + Ok(()) + } + } + + fn make_ctx() -> AnalysisContext { + AnalysisContext::new( + crate::context::BinarySource::Buffered( + Arc::from(vec![0u8; 4]), + ), + "deadbeef".into(), + "test.bin".into(), + 4, + ) + } + + #[test] + fn topological_sort_respects_dependencies() { + let log = Arc::new(Mutex::new(Vec::new())); + + let passes: Vec> = vec![ + Box::new(MockPass { + name: "c", + deps: vec!["b"], + log: Arc::clone(&log), + should_fail: false, + }), + Box::new(MockPass { + name: "a", + deps: vec![], + log: Arc::clone(&log), + should_fail: false, + }), + Box::new(MockPass { + name: "b", + deps: vec!["a"], + log: Arc::clone(&log), + should_fail: false, + }), + ]; + + let manager = PassManager::new(passes); + let mut ctx = make_ctx(); + let report = manager.run_all(&mut ctx); + + let execution_order = log.lock().unwrap().clone(); + assert_eq!(execution_order, vec!["a", "b", "c"]); + assert!(report.all_succeeded()); + assert_eq!(report.outcomes.len(), 3); + } + + #[test] + fn continues_on_failure() { + let log = Arc::new(Mutex::new(Vec::new())); + + let passes: Vec> = vec![ + Box::new(MockPass { + name: "first", + deps: vec![], + log: Arc::clone(&log), + should_fail: false, + }), + Box::new(MockPass { + name: "second", + deps: vec![], + log: Arc::clone(&log), + should_fail: true, + }), + Box::new(MockPass { + name: "third", + deps: vec![], + log: Arc::clone(&log), + should_fail: false, + }), + ]; + + let manager = PassManager::new(passes); + let mut ctx = make_ctx(); + let report = manager.run_all(&mut ctx); + + let execution_order = log.lock().unwrap().clone(); + assert_eq!( + execution_order, + vec!["first", "second", "third"] + ); + assert!(!report.all_succeeded()); + assert_eq!(report.failed_passes().len(), 1); + assert_eq!( + report.failed_passes()[0].name, + "second" + ); + } + + #[test] + fn diamond_dependency_ordering() { + let log = Arc::new(Mutex::new(Vec::new())); + + let passes: Vec> = vec![ + Box::new(MockPass { + name: "score", + deps: vec!["imports", "entropy"], + log: Arc::clone(&log), + should_fail: false, + }), + Box::new(MockPass { + name: "entropy", + deps: vec!["format"], + log: Arc::clone(&log), + should_fail: false, + }), + Box::new(MockPass { + name: "format", + deps: vec![], + log: Arc::clone(&log), + should_fail: false, + }), + Box::new(MockPass { + name: "imports", + deps: vec!["format"], + log: Arc::clone(&log), + should_fail: false, + }), + ]; + + let manager = PassManager::new(passes); + let mut ctx = make_ctx(); + manager.run_all(&mut ctx); + + let order = log.lock().unwrap().clone(); + let format_pos = + order.iter().position(|&n| n == "format").unwrap(); + let imports_pos = + order.iter().position(|&n| n == "imports").unwrap(); + let entropy_pos = + order.iter().position(|&n| n == "entropy").unwrap(); + let score_pos = + order.iter().position(|&n| n == "score").unwrap(); + + assert!(format_pos < imports_pos); + assert!(format_pos < entropy_pos); + assert!(imports_pos < score_pos); + assert!(entropy_pos < score_pos); + } + + #[test] + #[should_panic(expected = "cycle detected")] + fn detects_cycle() { + let log = Arc::new(Mutex::new(Vec::new())); + + let passes: Vec> = vec![ + Box::new(MockPass { + name: "a", + deps: vec!["b"], + log: Arc::clone(&log), + should_fail: false, + }), + Box::new(MockPass { + name: "b", + deps: vec!["a"], + log: Arc::clone(&log), + should_fail: false, + }), + ]; + + let _manager = PassManager::new(passes); + } + + #[test] + fn reports_duration() { + let log = Arc::new(Mutex::new(Vec::new())); + + let passes: Vec> = vec![ + Box::new(MockPass { + name: "fast", + deps: vec![], + log: Arc::clone(&log), + should_fail: false, + }), + ]; + + let manager = PassManager::new(passes); + let mut ctx = make_ctx(); + let report = manager.run_all(&mut ctx); + + assert_eq!(report.outcomes.len(), 1); + assert_eq!(report.outcomes[0].name, "fast"); + assert!(report.outcomes[0].success); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/disasm.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/disasm.rs new file mode 100644 index 0000000..790f205 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/disasm.rs @@ -0,0 +1,948 @@ +// ©AngelaMos | 2026 +// disasm.rs + +use std::collections::{ + BTreeMap, HashMap, HashSet, VecDeque, +}; + +use iced_x86::{ + Decoder, DecoderOptions, FlowControl, Formatter, + Instruction, IntelFormatter, +}; +use serde::{Deserialize, Serialize}; + +use crate::context::AnalysisContext; +use crate::error::EngineError; +use crate::formats::SectionInfo; +use crate::pass::{AnalysisPass, Sealed}; +use crate::types::{ + Architecture, CfgEdgeType, FlowControlType, +}; + +const MAX_FUNCTIONS: usize = 1000; +const MAX_INSTRUCTIONS: usize = 50_000; +const CFG_INSTRUCTION_LIMIT: usize = 500; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DisassemblyResult { + pub functions: Vec, + pub total_instructions: usize, + pub total_functions: usize, + pub architecture_bits: u8, + pub entry_function_address: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FunctionInfo { + pub address: u64, + pub name: Option, + pub size: u64, + pub instruction_count: usize, + pub basic_blocks: Vec, + pub is_entry_point: bool, + pub cfg: FunctionCfg, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BasicBlockInfo { + pub start_address: u64, + pub end_address: u64, + pub instruction_count: usize, + pub instructions: Vec, + pub successors: Vec, + pub predecessors: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstructionInfo { + pub address: u64, + pub bytes: Vec, + pub mnemonic: String, + pub operands: String, + pub size: u8, + pub flow_control: FlowControlType, +} + +#[derive( + Debug, Clone, Default, Serialize, Deserialize, +)] +pub struct FunctionCfg { + pub nodes: Vec, + pub edges: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CfgNode { + pub id: u64, + pub label: String, + pub instruction_count: usize, + pub instructions_preview: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CfgEdge { + pub from: u64, + pub to: u64, + pub edge_type: CfgEdgeType, +} + +pub struct DisasmPass; + +impl Sealed for DisasmPass {} + +impl AnalysisPass for DisasmPass { + fn name(&self) -> &'static str { + "disasm" + } + + fn dependencies(&self) -> &[&'static str] { + &["format"] + } + + fn run( + &self, + ctx: &mut AnalysisContext, + ) -> Result<(), EngineError> { + let format_result = ctx + .format_result + .as_ref() + .ok_or_else(|| EngineError::MissingDependency { + pass: "disasm".into(), + dependency: "format".into(), + })?; + + let arch = &format_result.architecture; + let bits = match arch { + Architecture::X86 => 32u32, + Architecture::X86_64 => 64, + _ => { + ctx.disassembly_result = + Some(empty_result( + format_result.bits, + format_result.entry_point, + )); + return Ok(()); + } + }; + + let data = ctx.data(); + let sections = &format_result.sections; + let entry_point = format_result.entry_point; + + let mut seeds = vec![entry_point]; + seeds.extend_from_slice( + &format_result.function_hints, + ); + + let result = disassemble( + data, + sections, + bits, + entry_point, + &seeds, + ); + ctx.disassembly_result = Some(result); + Ok(()) + } +} + +fn empty_result( + bits: u8, + entry_point: u64, +) -> DisassemblyResult { + DisassemblyResult { + functions: Vec::new(), + total_instructions: 0, + total_functions: 0, + architecture_bits: bits, + entry_function_address: entry_point, + } +} + +fn disassemble( + data: &[u8], + sections: &[SectionInfo], + bits: u32, + entry_point: u64, + seeds: &[u64], +) -> DisassemblyResult { + let exec_sections: Vec<&SectionInfo> = sections + .iter() + .filter(|s| s.permissions.execute && s.raw_size > 0) + .collect(); + + let mut functions = Vec::new(); + let mut visited_functions = HashSet::new(); + let mut total_instructions = 0; + let mut function_queue: VecDeque = + seeds.iter().copied().collect(); + + while let Some(func_addr) = function_queue.pop_front() + { + if functions.len() >= MAX_FUNCTIONS + || total_instructions >= MAX_INSTRUCTIONS + { + break; + } + if !visited_functions.insert(func_addr) { + continue; + } + if vaddr_to_offset(sections, func_addr).is_none() + { + continue; + } + + let (func_info, discovered_calls) = + disassemble_function( + data, + sections, + &exec_sections, + bits, + func_addr, + func_addr == entry_point, + MAX_INSTRUCTIONS - total_instructions, + ); + + total_instructions += func_info.instruction_count; + functions.push(func_info); + + for call_target in discovered_calls { + if !visited_functions.contains(&call_target) { + function_queue.push_back(call_target); + } + } + } + + let total_functions = functions.len(); + + DisassemblyResult { + functions, + total_instructions, + total_functions, + architecture_bits: bits as u8, + entry_function_address: entry_point, + } +} + +fn disassemble_function( + data: &[u8], + all_sections: &[SectionInfo], + exec_sections: &[&SectionInfo], + bits: u32, + func_addr: u64, + is_entry_point: bool, + instruction_budget: usize, +) -> (FunctionInfo, Vec) { + let mut decoded: BTreeMap = + BTreeMap::new(); + let mut block_leaders: HashSet = HashSet::new(); + let mut worklist: VecDeque = VecDeque::new(); + let mut visited: HashSet = HashSet::new(); + let mut discovered_calls: Vec = Vec::new(); + let mut formatter = IntelFormatter::new(); + + block_leaders.insert(func_addr); + worklist.push_back(func_addr); + + while let Some(addr) = worklist.pop_front() { + if !visited.insert(addr) { + continue; + } + if decoded.len() >= instruction_budget { + break; + } + + let offset = match vaddr_to_offset( + all_sections, + addr, + ) { + Some(o) => o as usize, + None => continue, + }; + + if !is_in_exec_section(exec_sections, addr) { + continue; + } + + let remaining = data.len().saturating_sub(offset); + if remaining == 0 { + continue; + } + + let slice = &data[offset..]; + let mut decoder = Decoder::with_ip( + bits, + slice, + addr, + DecoderOptions::NONE, + ); + let mut instr = Instruction::default(); + + while decoder.can_decode() + && decoded.len() < instruction_budget + { + decoder.decode_out(&mut instr); + let ip = instr.ip(); + + if ip != addr && visited.contains(&ip) { + break; + } + + if ip != addr + && block_leaders.contains(&ip) + { + break; + } + + let fc = instr.flow_control(); + let mnemonic = format!("{:?}", instr.mnemonic()) + .to_ascii_lowercase(); + + let mut operands_str = String::new(); + formatter + .format(&instr, &mut operands_str); + let operands = operands_str + .split_once(' ') + .map_or(String::new(), |(_, ops)| { + ops.to_string() + }); + + let instr_bytes = &data + [offset + (ip - addr) as usize + ..offset + + (ip - addr) as usize + + instr.len()]; + + let flow_type = map_flow_control(fc); + + decoded.insert( + ip, + DecodedInstruction { + info: InstructionInfo { + address: ip, + bytes: instr_bytes.to_vec(), + mnemonic, + operands, + size: instr.len() as u8, + flow_control: flow_type, + }, + next_ip: instr.next_ip(), + branch_target: None, + fallthrough: None, + }, + ); + + match fc { + FlowControl::ConditionalBranch => { + let target = + instr.near_branch_target(); + let fall = instr.next_ip(); + + if let Some(di) = + decoded.get_mut(&ip) + { + di.branch_target = Some(target); + di.fallthrough = Some(fall); + } + + block_leaders.insert(target); + block_leaders.insert(fall); + worklist.push_back(target); + worklist.push_back(fall); + break; + } + FlowControl::UnconditionalBranch => { + let target = + instr.near_branch_target(); + if let Some(di) = + decoded.get_mut(&ip) + { + di.branch_target = Some(target); + } + block_leaders.insert(target); + worklist.push_back(target); + break; + } + FlowControl::Return + | FlowControl::Interrupt + | FlowControl::IndirectBranch + | FlowControl::Exception => { + break; + } + FlowControl::Call => { + let target = + instr.near_branch_target(); + if target != 0 { + discovered_calls.push(target); + } + } + FlowControl::IndirectCall => {} + FlowControl::Next + | FlowControl::XbeginXabortXend => {} + } + } + } + + let basic_blocks = build_basic_blocks( + &decoded, + &block_leaders, + ); + let instruction_count: usize = basic_blocks + .iter() + .map(|bb| bb.instruction_count) + .sum(); + + let size = if let (Some(first), Some(last)) = ( + decoded.keys().next(), + decoded.keys().next_back(), + ) { + if let Some(last_instr) = decoded.get(last) { + last_instr.info.address + + last_instr.info.size as u64 + - first + } else { + 0 + } + } else { + 0 + }; + + let cfg = if instruction_count <= CFG_INSTRUCTION_LIMIT + { + build_cfg(&basic_blocks) + } else { + FunctionCfg::default() + }; + + let func = FunctionInfo { + address: func_addr, + name: None, + size, + instruction_count, + basic_blocks, + is_entry_point, + cfg, + }; + + (func, discovered_calls) +} + +struct DecodedInstruction { + info: InstructionInfo, + next_ip: u64, + branch_target: Option, + fallthrough: Option, +} + +fn build_basic_blocks( + decoded: &BTreeMap, + leaders: &HashSet, +) -> Vec { + if decoded.is_empty() { + return Vec::new(); + } + + let mut blocks: Vec = Vec::new(); + let mut current_instrs: Vec = + Vec::new(); + let mut block_start: Option = None; + + for (&addr, di) in decoded { + if leaders.contains(&addr) + && !current_instrs.is_empty() + { + let bb = finalize_block( + ¤t_instrs, + block_start.unwrap_or(addr), + decoded, + leaders, + ); + blocks.push(bb); + current_instrs.clear(); + block_start = None; + } + + if block_start.is_none() { + block_start = Some(addr); + } + current_instrs.push(di.info.clone()); + + let is_terminator = matches!( + di.info.flow_control, + FlowControlType::Branch + | FlowControlType::ConditionalBranch + | FlowControlType::Return + | FlowControlType::Interrupt + ); + if is_terminator { + let bb = finalize_block( + ¤t_instrs, + block_start.unwrap_or(addr), + decoded, + leaders, + ); + blocks.push(bb); + current_instrs.clear(); + block_start = None; + } + } + + if !current_instrs.is_empty() { + if let Some(start) = block_start { + let bb = finalize_block( + ¤t_instrs, + start, + decoded, + leaders, + ); + blocks.push(bb); + } + } + + let block_starts: HashSet = + blocks.iter().map(|b| b.start_address).collect(); + + for block in &mut blocks { + block + .successors + .retain(|s| block_starts.contains(s)); + } + + let predecessor_map: HashMap> = { + let mut map: HashMap> = + HashMap::new(); + for block in &blocks { + for &succ in &block.successors { + map.entry(succ) + .or_default() + .push(block.start_address); + } + } + map + }; + + for block in &mut blocks { + block.predecessors = predecessor_map + .get(&block.start_address) + .cloned() + .unwrap_or_default(); + } + + blocks +} + +fn finalize_block( + instructions: &[InstructionInfo], + start: u64, + decoded: &BTreeMap, + leaders: &HashSet, +) -> BasicBlockInfo { + let last = instructions.last().unwrap(); + let end_address = + last.address + last.size as u64 - 1; + + let mut successors = Vec::new(); + let last_addr = last.address; + if let Some(di) = decoded.get(&last_addr) { + if let Some(target) = di.branch_target { + successors.push(target); + } + if let Some(fall) = di.fallthrough { + successors.push(fall); + } else if !matches!( + di.info.flow_control, + FlowControlType::Branch + | FlowControlType::Return + | FlowControlType::Interrupt + ) { + let next = di.next_ip; + if leaders.contains(&next) + || decoded.contains_key(&next) + { + successors.push(next); + } + } + } + + BasicBlockInfo { + start_address: start, + end_address, + instruction_count: instructions.len(), + instructions: instructions.to_vec(), + successors, + predecessors: Vec::new(), + } +} + +fn build_cfg( + blocks: &[BasicBlockInfo], +) -> FunctionCfg { + let mut nodes = Vec::new(); + let mut edges = Vec::new(); + + for block in blocks { + let preview = if block.instructions.is_empty() { + String::new() + } else if block.instructions.len() == 1 { + block.instructions[0].mnemonic.clone() + } else { + format!( + "{} ... {}", + block.instructions[0].mnemonic, + block.instructions.last().unwrap().mnemonic + ) + }; + + nodes.push(CfgNode { + id: block.start_address, + label: format!( + "0x{:x}", + block.start_address + ), + instruction_count: block.instruction_count, + instructions_preview: preview, + }); + + let last_instr = block.instructions.last(); + for &succ in &block.successors { + let edge_type = + if let Some(last) = last_instr { + match last.flow_control { + FlowControlType::ConditionalBranch => { + if succ + == block + .successors + .first() + .copied() + .unwrap_or(0) + { + CfgEdgeType::ConditionalTrue + } else { + CfgEdgeType::ConditionalFalse + } + } + FlowControlType::Branch => { + CfgEdgeType::Unconditional + } + _ => CfgEdgeType::Fallthrough, + } + } else { + CfgEdgeType::Fallthrough + }; + + edges.push(CfgEdge { + from: block.start_address, + to: succ, + edge_type, + }); + } + } + + FunctionCfg { nodes, edges } +} + +fn vaddr_to_offset( + sections: &[SectionInfo], + vaddr: u64, +) -> Option { + sections.iter().find_map(|s| { + if s.raw_size > 0 + && vaddr >= s.virtual_address + && vaddr + < s.virtual_address + s.virtual_size + { + Some( + s.raw_offset + + (vaddr - s.virtual_address), + ) + } else { + None + } + }) +} + +fn is_in_exec_section( + exec_sections: &[&SectionInfo], + vaddr: u64, +) -> bool { + exec_sections.iter().any(|s| { + vaddr >= s.virtual_address + && vaddr + < s.virtual_address + s.virtual_size + }) +} + +fn map_flow_control( + fc: FlowControl, +) -> FlowControlType { + match fc { + FlowControl::Next + | FlowControl::XbeginXabortXend => { + FlowControlType::Next + } + FlowControl::UnconditionalBranch + | FlowControl::IndirectBranch => { + FlowControlType::Branch + } + FlowControl::ConditionalBranch => { + FlowControlType::ConditionalBranch + } + FlowControl::Call + | FlowControl::IndirectCall => { + FlowControlType::Call + } + FlowControl::Return => FlowControlType::Return, + FlowControl::Interrupt + | FlowControl::Exception => { + FlowControlType::Interrupt + } + } +} + +pub fn disassemble_code( + code: &[u8], + base_addr: u64, + bits: u32, +) -> Vec { + let mut decoder = Decoder::with_ip( + bits, + code, + base_addr, + DecoderOptions::NONE, + ); + let mut formatter = IntelFormatter::new(); + let mut instr = Instruction::default(); + let mut result = Vec::new(); + + while decoder.can_decode() { + decoder.decode_out(&mut instr); + let mnemonic = format!( + "{:?}", + instr.mnemonic() + ) + .to_ascii_lowercase(); + + let mut full = String::new(); + formatter.format(&instr, &mut full); + let operands = full + .split_once(' ') + .map_or(String::new(), |(_, ops)| { + ops.to_string() + }); + + let start = + (instr.ip() - base_addr) as usize; + let bytes = + code[start..start + instr.len()].to_vec(); + + result.push(InstructionInfo { + address: instr.ip(), + bytes, + mnemonic, + operands, + size: instr.len() as u8, + flow_control: map_flow_control( + instr.flow_control(), + ), + }); + } + + result +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use super::*; + use crate::context::BinarySource; + use crate::types::SectionPermissions; + + fn load_fixture(name: &str) -> Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path).unwrap_or_else(|e| { + panic!("fixture {path}: {e}") + }) + } + + fn make_ctx(data: Vec) -> AnalysisContext { + let size = data.len() as u64; + AnalysisContext::new( + BinarySource::Buffered(Arc::from(data)), + "deadbeef".into(), + "test.bin".into(), + size, + ) + } + + #[test] + fn disassemble_simple_function() { + let code: &[u8] = &[ + 0x55, 0x48, 0x89, 0xE5, 0x31, 0xC0, + 0x5D, 0xC3, + ]; + let instrs = + disassemble_code(code, 0x1000, 64); + assert_eq!(instrs.len(), 5); + assert_eq!(instrs[0].mnemonic, "push"); + assert_eq!(instrs[4].mnemonic, "ret"); + assert_eq!( + instrs[4].flow_control, + FlowControlType::Return + ); + } + + #[test] + fn basic_block_split_on_branch() { + let code: &[u8] = &[ + 0x31, 0xC0, 0x85, 0xC0, 0x74, 0x02, + 0x31, 0xC9, 0xC3, + ]; + + let sections = vec![SectionInfo { + name: ".text".into(), + virtual_address: 0x1000, + virtual_size: code.len() as u64, + raw_offset: 0, + raw_size: code.len() as u64, + permissions: SectionPermissions { + read: true, + write: false, + execute: true, + }, + sha256: String::new(), + }]; + + let result = disassemble( + code, + §ions, + 64, + 0x1000, + &[0x1000], + ); + assert!(!result.functions.is_empty()); + let func = &result.functions[0]; + assert!( + func.basic_blocks.len() >= 2, + "conditional branch should create multiple blocks, got {}", + func.basic_blocks.len() + ); + } + + #[test] + fn cfg_edges_conditional() { + let code: &[u8] = &[ + 0x31, 0xC0, 0x85, 0xC0, 0x74, 0x02, + 0x31, 0xC9, 0xC3, + ]; + + let sections = vec![SectionInfo { + name: ".text".into(), + virtual_address: 0x1000, + virtual_size: code.len() as u64, + raw_offset: 0, + raw_size: code.len() as u64, + permissions: SectionPermissions { + read: true, + write: false, + execute: true, + }, + sha256: String::new(), + }]; + + let result = disassemble( + code, + §ions, + 64, + 0x1000, + &[0x1000], + ); + let func = &result.functions[0]; + assert!( + !func.cfg.edges.is_empty(), + "CFG should have edges" + ); + assert!( + !func.cfg.nodes.is_empty(), + "CFG should have nodes" + ); + } + + #[test] + fn non_x86_returns_empty() { + let data = vec![0u8; 64]; + let mut ctx = make_ctx(data); + ctx.format_result = + Some(crate::formats::FormatResult { + format: crate::types::BinaryFormat::Elf, + architecture: Architecture::Aarch64, + bits: 64, + endianness: + crate::types::Endianness::Little, + entry_point: 0x1000, + is_stripped: false, + is_pie: false, + has_debug_info: false, + sections: Vec::new(), + segments: Vec::new(), + anomalies: Vec::new(), + pe_info: None, + elf_info: None, + macho_info: None, + function_hints: Vec::new(), + }); + + DisasmPass.run(&mut ctx).unwrap(); + let result = ctx.disassembly_result.unwrap(); + assert!(result.functions.is_empty()); + assert_eq!(result.total_instructions, 0); + } + + #[test] + fn elf_disassembly() { + let data = load_fixture("hello_elf"); + let mut ctx = make_ctx(data); + + crate::passes::format::FormatPass + .run(&mut ctx) + .unwrap(); + DisasmPass.run(&mut ctx).unwrap(); + + let result = + ctx.disassembly_result.as_ref().unwrap(); + assert!( + result.total_functions > 0, + "should find at least one function" + ); + assert!(result.total_instructions > 0); + + let entry_func = result.functions.iter().find( + |f| { + f.address + == result.entry_function_address + }, + ); + assert!( + entry_func.is_some() + || !result.functions.is_empty(), + "should have disassembled functions" + ); + } + + #[test] + fn disasm_pass_populates_context() { + let data = load_fixture("hello_elf"); + let mut ctx = make_ctx(data); + + crate::passes::format::FormatPass + .run(&mut ctx) + .unwrap(); + assert!(ctx.disassembly_result.is_none()); + + DisasmPass.run(&mut ctx).unwrap(); + assert!(ctx.disassembly_result.is_some()); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/entropy.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/entropy.rs new file mode 100644 index 0000000..423d206 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/entropy.rs @@ -0,0 +1,540 @@ +// ©AngelaMos | 2026 +// entropy.rs + +use serde::{Deserialize, Serialize}; + +use crate::context::AnalysisContext; +use crate::error::EngineError; +use crate::formats::SectionInfo; +use crate::pass::{AnalysisPass, Sealed}; +use crate::types::{ + EntropyClassification, EntropyFlag, +}; + +const PLAINTEXT_MAX: f64 = 3.5; +const NATIVE_CODE_MAX: f64 = 6.0; +const COMPRESSED_MAX: f64 = 7.0; +const PACKED_MAX: f64 = 7.2; + +const HIGH_ENTROPY_THRESHOLD: f64 = 7.0; +const VIRTUAL_RAW_RATIO_THRESHOLD: f64 = 10.0; + +const BYTE_RANGE: usize = 256; + +const STRUCTURAL_INDICATORS_FOR_PACKING: usize = 2; + +const PUSHAD_OPCODE: u8 = 0x60; + +const PACKER_SECTION_NAMES: &[(&str, &str)] = &[ + ("UPX0", "UPX"), + ("UPX1", "UPX"), + ("UPX2", "UPX"), + (".themida", "Themida"), + (".vmp0", "VMProtect"), + (".vmp1", "VMProtect"), + (".vmp2", "VMProtect"), + (".aspack", "ASPack"), + (".adata", "ASPack"), + ("PEC2TO", "PECompact"), + ("PEC2", "PECompact"), + ("pec1", "PECompact"), + (".MPRESS1", "MPRESS"), + (".MPRESS2", "MPRESS"), + (".enigma1", "Enigma"), + (".enigma2", "Enigma"), +]; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EntropyResult { + pub overall_entropy: f64, + pub sections: Vec, + pub packing_detected: bool, + pub packer_name: Option, + pub packing_indicators: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SectionEntropy { + pub name: String, + pub entropy: f64, + pub size: u64, + pub classification: EntropyClassification, + pub virtual_to_raw_ratio: f64, + pub is_anomalous: bool, + pub flags: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PackingIndicator { + pub indicator_type: String, + pub description: String, + pub evidence: String, + pub packer_name: Option, +} + +pub struct EntropyPass; + +impl Sealed for EntropyPass {} + +impl AnalysisPass for EntropyPass { + fn name(&self) -> &'static str { + "entropy" + } + + fn dependencies(&self) -> &[&'static str] { + &["format"] + } + + fn run( + &self, + ctx: &mut AnalysisContext, + ) -> Result<(), EngineError> { + let format_result = ctx + .format_result + .as_ref() + .ok_or_else(|| EngineError::MissingDependency { + pass: "entropy".into(), + dependency: "format".into(), + })?; + + let data = ctx.data(); + let result = analyze_entropy( + data, + &format_result.sections, + format_result.entry_point, + ); + ctx.entropy_result = Some(result); + Ok(()) + } +} + +fn analyze_entropy( + data: &[u8], + sections: &[SectionInfo], + entry_point: u64, +) -> EntropyResult { + let overall_entropy = shannon_entropy(data); + let mut section_entropies = Vec::new(); + let mut packing_indicators = Vec::new(); + let mut packer_name: Option = None; + let mut structural_count = 0; + + for section in sections { + let section_data = read_section_data( + data, + section.raw_offset, + section.raw_size, + ); + let entropy = if section_data.is_empty() { + 0.0 + } else { + shannon_entropy(section_data) + }; + let classification = classify_entropy(entropy); + let vr_ratio = if section.raw_size > 0 { + section.virtual_size as f64 + / section.raw_size as f64 + } else { + 0.0 + }; + + let mut flags = Vec::new(); + + if entropy > HIGH_ENTROPY_THRESHOLD { + flags.push(EntropyFlag::HighEntropy); + } + if vr_ratio > VIRTUAL_RAW_RATIO_THRESHOLD { + flags.push(EntropyFlag::HighVirtualToRawRatio); + } + if section.raw_size == 0 + && section.virtual_size > 0 + { + flags.push(EntropyFlag::EmptyRawData); + } + if section.permissions.is_rwx() { + flags.push(EntropyFlag::Rwx); + } + + if let Some(packer) = + detect_packer_by_section(§ion.name) + { + flags.push(EntropyFlag::PackerSectionName); + packing_indicators.push(PackingIndicator { + indicator_type: "section_name".into(), + description: format!( + "Section name matches {packer} packer" + ), + evidence: section.name.clone(), + packer_name: Some(packer.into()), + }); + if packer_name.is_none() { + packer_name = Some(packer.into()); + } + } + + if section.raw_size == 0 + && section.virtual_size > 0 + && section.permissions.execute + { + structural_count += 1; + packing_indicators.push(PackingIndicator { + indicator_type: "structural".into(), + description: + "Empty raw data with executable \ + virtual section" + .into(), + evidence: format!( + "section={} raw=0 virtual={}", + section.name, section.virtual_size + ), + packer_name: None, + }); + } + + if vr_ratio > VIRTUAL_RAW_RATIO_THRESHOLD { + structural_count += 1; + packing_indicators.push(PackingIndicator { + indicator_type: "structural".into(), + description: + "High virtual to raw size ratio" + .into(), + evidence: format!( + "section={} ratio={vr_ratio:.1}", + section.name + ), + packer_name: None, + }); + } + + let is_anomalous = !flags.is_empty(); + + section_entropies.push(SectionEntropy { + name: section.name.clone(), + entropy, + size: section.raw_size, + classification, + virtual_to_raw_ratio: vr_ratio, + is_anomalous, + flags, + }); + } + + if let Some(ep_section) = find_ep_section( + sections, + entry_point, + ) { + let ep_file_offset = entry_point + .wrapping_sub(ep_section.virtual_address) + .wrapping_add(ep_section.raw_offset); + if let Some(&first_byte) = + data.get(ep_file_offset as usize) + { + if first_byte == PUSHAD_OPCODE { + packing_indicators.push( + PackingIndicator { + indicator_type: "entry_point" + .into(), + description: + "PUSHAD at entry point" + .into(), + evidence: format!( + "byte 0x{PUSHAD_OPCODE:02x} \ + at EP offset 0x{ep_file_offset:x}" + ), + packer_name: None, + }, + ); + } + } + } + + let packing_detected = packer_name.is_some() + || structural_count + >= STRUCTURAL_INDICATORS_FOR_PACKING; + + EntropyResult { + overall_entropy, + sections: section_entropies, + packing_detected, + packer_name, + packing_indicators, + } +} + +fn shannon_entropy(data: &[u8]) -> f64 { + if data.is_empty() { + return 0.0; + } + let mut freq = [0u64; BYTE_RANGE]; + for &byte in data { + freq[byte as usize] += 1; + } + let len = data.len() as f64; + freq.iter() + .filter(|&&c| c > 0) + .map(|&c| { + let p = c as f64 / len; + -p * p.log2() + }) + .sum() +} + +fn classify_entropy( + entropy: f64, +) -> EntropyClassification { + if entropy < PLAINTEXT_MAX { + EntropyClassification::Plaintext + } else if entropy < NATIVE_CODE_MAX { + EntropyClassification::NativeCode + } else if entropy < COMPRESSED_MAX { + EntropyClassification::Compressed + } else if entropy < PACKED_MAX { + EntropyClassification::Packed + } else { + EntropyClassification::Encrypted + } +} + +fn detect_packer_by_section( + name: &str, +) -> Option<&'static str> { + PACKER_SECTION_NAMES + .iter() + .find(|&&(section_name, _)| section_name == name) + .map(|&(_, packer)| packer) +} + +fn read_section_data( + data: &[u8], + offset: u64, + size: u64, +) -> &[u8] { + if size == 0 { + return &[]; + } + let start = offset as usize; + let end = start.saturating_add(size as usize); + if start >= data.len() || end > data.len() { + return &[]; + } + &data[start..end] +} + +fn find_ep_section( + sections: &[SectionInfo], + entry_point: u64, +) -> Option<&SectionInfo> { + sections.iter().find(|s| { + entry_point >= s.virtual_address + && entry_point + < s.virtual_address + s.virtual_size + }) +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use super::*; + use crate::context::BinarySource; + use crate::types::SectionPermissions; + + fn load_fixture(name: &str) -> Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path).unwrap_or_else(|e| { + panic!("fixture {path}: {e}") + }) + } + + fn make_ctx(data: Vec) -> AnalysisContext { + let size = data.len() as u64; + AnalysisContext::new( + BinarySource::Buffered(Arc::from(data)), + "deadbeef".into(), + "test.bin".into(), + size, + ) + } + + #[test] + fn entropy_all_zeros() { + let data = vec![0u8; 1024]; + assert!(shannon_entropy(&data) < 0.001); + } + + #[test] + fn entropy_uniform_distribution() { + let data: Vec = + (0..=255u8).cycle().take(1024).collect(); + let e = shannon_entropy(&data); + assert!( + (e - 8.0).abs() < 0.01, + "uniform distribution should be ~8.0, got {e}" + ); + } + + #[test] + fn entropy_empty_data() { + assert!( + shannon_entropy(&[]) < 0.001, + "empty data should have zero entropy" + ); + } + + #[test] + fn entropy_classification_thresholds() { + assert_eq!( + classify_entropy(2.0), + EntropyClassification::Plaintext + ); + assert_eq!( + classify_entropy(5.0), + EntropyClassification::NativeCode + ); + assert_eq!( + classify_entropy(6.5), + EntropyClassification::Compressed + ); + assert_eq!( + classify_entropy(7.1), + EntropyClassification::Packed + ); + assert_eq!( + classify_entropy(7.5), + EntropyClassification::Encrypted + ); + } + + #[test] + fn packer_section_name_detection() { + assert_eq!( + detect_packer_by_section("UPX0"), + Some("UPX") + ); + assert_eq!( + detect_packer_by_section("UPX1"), + Some("UPX") + ); + assert_eq!( + detect_packer_by_section(".vmp0"), + Some("VMProtect") + ); + assert_eq!( + detect_packer_by_section(".themida"), + Some("Themida") + ); + assert_eq!( + detect_packer_by_section(".text"), + None + ); + } + + #[test] + fn section_flags_high_entropy() { + let sections = vec![SectionInfo { + name: ".text".into(), + virtual_address: 0x1000, + virtual_size: 0x1000, + raw_offset: 0, + raw_size: 256, + permissions: SectionPermissions { + read: true, + write: false, + execute: true, + }, + sha256: String::new(), + }]; + + let data: Vec = + (0..=255u8).cycle().take(256).collect(); + let result = + analyze_entropy(&data, §ions, 0x1000); + let text_section = &result.sections[0]; + assert!( + text_section.entropy > HIGH_ENTROPY_THRESHOLD + ); + assert!(text_section + .flags + .contains(&EntropyFlag::HighEntropy)); + assert!(text_section.is_anomalous); + } + + #[test] + fn packer_detection_by_section_name() { + let sections = vec![ + SectionInfo { + name: "UPX0".into(), + virtual_address: 0x1000, + virtual_size: 0x10000, + raw_offset: 0, + raw_size: 0, + permissions: SectionPermissions { + read: true, + write: true, + execute: true, + }, + sha256: String::new(), + }, + SectionInfo { + name: "UPX1".into(), + virtual_address: 0x11000, + virtual_size: 0x5000, + raw_offset: 0x200, + raw_size: 0x4000, + permissions: SectionPermissions { + read: true, + write: false, + execute: true, + }, + sha256: String::new(), + }, + ]; + + let data = vec![0u8; 0x4200]; + let result = + analyze_entropy(&data, §ions, 0x11000); + assert!(result.packing_detected); + assert_eq!( + result.packer_name, + Some("UPX".into()) + ); + assert!(!result.packing_indicators.is_empty()); + } + + #[test] + fn elf_entropy_analysis() { + let data = load_fixture("hello_elf"); + let format_result = + crate::formats::parse_format(&data) + .unwrap(); + let result = analyze_entropy( + &data, + &format_result.sections, + format_result.entry_point, + ); + + assert!(result.overall_entropy > 0.0); + assert!(!result.sections.is_empty()); + assert!(!result.packing_detected); + } + + #[test] + fn entropy_pass_populates_context() { + let data = load_fixture("hello_elf"); + let mut ctx = make_ctx(data); + + crate::passes::format::FormatPass + .run(&mut ctx) + .unwrap(); + assert!(ctx.format_result.is_some()); + + EntropyPass.run(&mut ctx).unwrap(); + assert!(ctx.entropy_result.is_some()); + + let result = ctx.entropy_result.unwrap(); + assert!(result.overall_entropy > 0.0); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/format.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/format.rs new file mode 100644 index 0000000..628d4bd --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/format.rs @@ -0,0 +1,183 @@ +// ©AngelaMos | 2026 +// format.rs + +use crate::context::AnalysisContext; +use crate::error::EngineError; +use crate::formats; +use crate::pass::{AnalysisPass, Sealed}; + +pub struct FormatPass; + +impl Sealed for FormatPass {} + +impl AnalysisPass for FormatPass { + fn name(&self) -> &'static str { + "format" + } + + fn dependencies(&self) -> &[&'static str] { + &[] + } + + fn run( + &self, + ctx: &mut AnalysisContext, + ) -> Result<(), EngineError> { + let result = + formats::parse_format(ctx.data())?; + ctx.format_result = Some(result); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use crate::context::{AnalysisContext, BinarySource}; + use crate::formats::{self, FormatAnomaly}; + use crate::types::{Architecture, BinaryFormat, Endianness}; + + fn load_fixture(name: &str) -> Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path).unwrap_or_else(|e| { + panic!("fixture {path}: {e}") + }) + } + + fn make_ctx(data: Vec) -> AnalysisContext { + let size = data.len() as u64; + AnalysisContext::new( + BinarySource::Buffered(Arc::from(data)), + "deadbeef".into(), + "test.bin".into(), + size, + ) + } + + #[test] + fn parse_elf_basic_metadata() { + let data = load_fixture("hello_elf"); + let result = + formats::parse_format(&data).unwrap(); + + assert_eq!(result.format, BinaryFormat::Elf); + assert_eq!( + result.architecture, + Architecture::X86_64 + ); + assert_eq!(result.bits, 64); + assert_eq!( + result.endianness, + Endianness::Little + ); + assert!(result.entry_point > 0); + assert!(result.is_pie); + assert!(!result.is_stripped); + } + + #[test] + fn parse_elf_sections_present() { + let data = load_fixture("hello_elf"); + let result = + formats::parse_format(&data).unwrap(); + + assert!(!result.sections.is_empty()); + let text = result + .sections + .iter() + .find(|s| s.name == ".text"); + assert!(text.is_some()); + assert!(text.unwrap().permissions.execute); + } + + #[test] + fn parse_elf_segments_present() { + let data = load_fixture("hello_elf"); + let result = + formats::parse_format(&data).unwrap(); + + assert!(!result.segments.is_empty()); + let load_segments: Vec<_> = result + .segments + .iter() + .filter(|s| { + s.name.as_deref() == Some("LOAD") + }) + .collect(); + assert!(!load_segments.is_empty()); + } + + #[test] + fn parse_elf_stripped_detection() { + let data = + load_fixture("hello_elf_stripped"); + let result = + formats::parse_format(&data).unwrap(); + + assert!(result.is_stripped); + assert!(result.anomalies.iter().any(|a| { + matches!( + a, + FormatAnomaly::StrippedBinary + ) + })); + } + + #[test] + fn parse_elf_info_populated() { + let data = load_fixture("hello_elf"); + let result = + formats::parse_format(&data).unwrap(); + + let elf_info = result.elf_info.unwrap(); + assert!(!elf_info.os_abi.is_empty()); + assert!(!elf_info.elf_type.is_empty()); + assert!(elf_info.interpreter.is_some()); + assert!(elf_info.gnu_relro); + } + + #[test] + fn parse_elf_section_hashes() { + let data = load_fixture("hello_elf"); + let result = + formats::parse_format(&data).unwrap(); + + let text = result + .sections + .iter() + .find(|s| s.name == ".text") + .unwrap(); + assert!( + !text.sha256.is_empty(), + ".text section should have a hash" + ); + assert_eq!(text.sha256.len(), 64); + } + + #[test] + fn parse_invalid_binary() { + let data = vec![0xDE, 0xAD, 0xBE, 0xEF]; + let result = formats::parse_format(&data); + assert!(result.is_err()); + } + + #[test] + fn format_pass_populates_context() { + use crate::pass::AnalysisPass; + use super::FormatPass; + + let data = load_fixture("hello_elf"); + let mut ctx = make_ctx(data); + assert!(ctx.format_result.is_none()); + + FormatPass.run(&mut ctx).unwrap(); + assert!(ctx.format_result.is_some()); + + let fmt = ctx.format_result.unwrap(); + assert_eq!(fmt.format, BinaryFormat::Elf); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/imports.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/imports.rs new file mode 100644 index 0000000..1a35d25 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/imports.rs @@ -0,0 +1,908 @@ +// ©AngelaMos | 2026 +// imports.rs + +use std::collections::HashSet; + +use serde::{Deserialize, Serialize}; + +use crate::context::AnalysisContext; +use crate::error::EngineError; +use crate::pass::{AnalysisPass, Sealed}; +use crate::types::Severity; + +pub struct SuspiciousApiDef { + pub name: &'static str, + pub tag: &'static str, + pub mitre_id: &'static str, +} + +pub const SUSPICIOUS_APIS: &[SuspiciousApiDef] = &[ + SuspiciousApiDef { + name: "VirtualAllocEx", + tag: "injection", + mitre_id: "T1055", + }, + SuspiciousApiDef { + name: "WriteProcessMemory", + tag: "injection", + mitre_id: "T1055", + }, + SuspiciousApiDef { + name: "CreateRemoteThread", + tag: "injection", + mitre_id: "T1055", + }, + SuspiciousApiDef { + name: "NtUnmapViewOfSection", + tag: "hollowing", + mitre_id: "T1055.012", + }, + SuspiciousApiDef { + name: "SetThreadContext", + tag: "hollowing", + mitre_id: "T1055.012", + }, + SuspiciousApiDef { + name: "QueueUserAPC", + tag: "apc-injection", + mitre_id: "T1055.004", + }, + SuspiciousApiDef { + name: "IsDebuggerPresent", + tag: "anti-debug", + mitre_id: "T1622", + }, + SuspiciousApiDef { + name: "NtQueryInformationProcess", + tag: "anti-debug", + mitre_id: "T1622", + }, + SuspiciousApiDef { + name: "OpenProcessToken", + tag: "token-manipulation", + mitre_id: "T1134", + }, + SuspiciousApiDef { + name: "AdjustTokenPrivileges", + tag: "token-manipulation", + mitre_id: "T1134", + }, + SuspiciousApiDef { + name: "RegSetValueEx", + tag: "persistence", + mitre_id: "T1547.001", + }, + SuspiciousApiDef { + name: "CreateService", + tag: "persistence", + mitre_id: "T1543.003", + }, + SuspiciousApiDef { + name: "URLDownloadToFile", + tag: "download", + mitre_id: "T1105", + }, + SuspiciousApiDef { + name: "InternetOpen", + tag: "network", + mitre_id: "T1071", + }, + SuspiciousApiDef { + name: "CryptDecrypt", + tag: "deobfuscation", + mitre_id: "T1140", + }, + SuspiciousApiDef { + name: "ptrace", + tag: "injection", + mitre_id: "T1055.008", + }, + SuspiciousApiDef { + name: "mprotect", + tag: "memory-manipulation", + mitre_id: "", + }, + SuspiciousApiDef { + name: "dlopen", + tag: "loading", + mitre_id: "T1574.006", + }, + SuspiciousApiDef { + name: "dlsym", + tag: "loading", + mitre_id: "T1574.006", + }, + SuspiciousApiDef { + name: "execve", + tag: "execution", + mitre_id: "T1059", + }, + SuspiciousApiDef { + name: "process_vm_readv", + tag: "injection", + mitre_id: "T1055", + }, + SuspiciousApiDef { + name: "process_vm_writev", + tag: "injection", + mitre_id: "T1055", + }, +]; + +struct CombinationDef { + name: &'static str, + description: &'static str, + patterns: &'static [&'static str], + mitre_id: &'static str, + severity: Severity, +} + +const SUSPICIOUS_COMBINATIONS: &[CombinationDef] = &[ + CombinationDef { + name: "Process Injection Chain", + description: "VirtualAllocEx + WriteProcessMemory \ + + CreateRemoteThread", + patterns: &[ + "VirtualAllocEx", + "WriteProcessMemory", + "CreateRemoteThread", + ], + mitre_id: "T1055", + severity: Severity::Critical, + }, + CombinationDef { + name: "Process Hollowing", + description: "CreateProcess + \ + NtUnmapViewOfSection + \ + SetThreadContext + ResumeThread", + patterns: &[ + "CreateProcess*", + "NtUnmapViewOfSection", + "SetThreadContext", + "ResumeThread", + ], + mitre_id: "T1055.012", + severity: Severity::Critical, + }, + CombinationDef { + name: "APC Injection", + description: "QueueUserAPC + OpenThread", + patterns: &["QueueUserAPC", "OpenThread"], + mitre_id: "T1055.004", + severity: Severity::High, + }, + CombinationDef { + name: "DLL Injection", + description: "LoadLibrary + CreateRemoteThread", + patterns: &[ + "LoadLibrary*", + "CreateRemoteThread", + ], + mitre_id: "T1055.001", + severity: Severity::High, + }, + CombinationDef { + name: "Credential Theft", + description: "OpenProcess + ReadProcessMemory", + patterns: &[ + "OpenProcess", + "ReadProcessMemory", + ], + mitre_id: "T1003", + severity: Severity::Critical, + }, + CombinationDef { + name: "Service Persistence", + description: "OpenSCManager + CreateService", + patterns: &[ + "OpenSCManager*", + "CreateService*", + ], + mitre_id: "T1543.003", + severity: Severity::Medium, + }, + CombinationDef { + name: "Registry Persistence", + description: "RegOpenKeyEx + RegSetValueEx", + patterns: &[ + "RegOpenKeyEx*", + "RegSetValueEx*", + ], + mitre_id: "T1547.001", + severity: Severity::Medium, + }, + CombinationDef { + name: "Download and Execute", + description: "URLDownloadToFile + ShellExecute", + patterns: &[ + "URLDownloadToFile*", + "ShellExecute*", + ], + mitre_id: "T1105", + severity: Severity::High, + }, + CombinationDef { + name: "Download and Execute", + description: "URLDownloadToFile + WinExec", + patterns: &["URLDownloadToFile*", "WinExec"], + mitre_id: "T1105", + severity: Severity::High, + }, + CombinationDef { + name: "Linux ptrace Injection", + description: "ptrace-based process injection", + patterns: &["ptrace"], + mitre_id: "T1055.008", + severity: Severity::High, + }, + CombinationDef { + name: "Linux RWX Memory", + description: "mmap + mprotect for RWX memory", + patterns: &["mmap", "mprotect"], + mitre_id: "", + severity: Severity::Medium, + }, + CombinationDef { + name: "Linux C2 Connection", + description: "socket + connect + inet_pton \ + hardcoded C2 address", + patterns: &[ + "socket", + "connect", + "inet_pton", + ], + mitre_id: "T1071", + severity: Severity::High, + }, + CombinationDef { + name: "Linux Network Listener", + description: "socket + bind + listen + accept \ + backdoor listener", + patterns: &[ + "socket", + "bind", + "listen", + "accept", + ], + mitre_id: "T1571", + severity: Severity::High, + }, + CombinationDef { + name: "Linux Dynamic Loading", + description: "dlopen + dlsym runtime \ + API resolution", + patterns: &["dlopen", "dlsym"], + mitre_id: "T1574.006", + severity: Severity::Medium, + }, + CombinationDef { + name: "Linux Process Injection", + description: "process_vm_writev \ + cross-process memory write", + patterns: &["process_vm_writev"], + mitre_id: "T1055", + severity: Severity::Critical, + }, +]; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ImportResult { + pub imports: Vec, + pub exports: Vec, + pub libraries: Vec, + pub suspicious_combinations: + Vec, + pub mitre_mappings: Vec, + pub statistics: ImportStatistics, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ImportEntry { + pub library: String, + pub function: String, + pub address: Option, + pub ordinal: Option, + pub is_suspicious: bool, + pub threat_tags: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExportEntry { + pub name: Option, + pub address: u64, + pub ordinal: Option, + pub is_forwarded: bool, + pub forward_target: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SuspiciousCombination { + pub name: String, + pub description: String, + pub apis: Vec, + pub mitre_id: String, + pub severity: Severity, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MitreMapping { + pub technique_id: String, + pub api: String, + pub tag: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ImportStatistics { + pub total_imports: usize, + pub total_exports: usize, + pub suspicious_count: usize, + pub library_count: usize, +} + +pub struct ImportPass; + +impl Sealed for ImportPass {} + +impl AnalysisPass for ImportPass { + fn name(&self) -> &'static str { + "imports" + } + + fn dependencies(&self) -> &[&'static str] { + &["format"] + } + + fn run( + &self, + ctx: &mut AnalysisContext, + ) -> Result<(), EngineError> { + let result = analyze_imports(ctx.data())?; + ctx.import_result = Some(result); + Ok(()) + } +} + +fn analyze_imports( + data: &[u8], +) -> Result { + let object = + goblin::Object::parse(data).map_err(|e| { + EngineError::InvalidBinary { + reason: e.to_string(), + } + })?; + + let (imports, exports, libraries) = match &object { + goblin::Object::Elf(elf) => extract_elf(elf), + goblin::Object::PE(pe) => extract_pe(pe), + goblin::Object::Mach(mach) => { + extract_mach(mach, data)? + } + _ => (Vec::new(), Vec::new(), Vec::new()), + }; + + let suspicious_combinations = + detect_combinations(&imports); + let mitre_mappings = + collect_mitre_mappings(&imports); + let suspicious_count = imports + .iter() + .filter(|i| i.is_suspicious) + .count(); + + let statistics = ImportStatistics { + total_imports: imports.len(), + total_exports: exports.len(), + suspicious_count, + library_count: libraries.len(), + }; + + Ok(ImportResult { + imports, + exports, + libraries, + suspicious_combinations, + mitre_mappings, + statistics, + }) +} + +fn extract_elf( + elf: &goblin::elf::Elf, +) -> (Vec, Vec, Vec) { + let libraries: Vec = elf + .libraries + .iter() + .map(|s| s.to_string()) + .collect(); + + let mut imports = Vec::new(); + for sym in elf.dynsyms.iter() { + if !sym.is_import() || sym.st_name == 0 { + continue; + } + let name = elf + .dynstrtab + .get_at(sym.st_name) + .unwrap_or(""); + if name.is_empty() { + continue; + } + let (is_suspicious, threat_tags) = + flag_suspicious(name); + imports.push(ImportEntry { + library: String::new(), + function: name.to_string(), + address: None, + ordinal: None, + is_suspicious, + threat_tags, + }); + } + + let mut exports = Vec::new(); + for sym in elf.dynsyms.iter() { + if sym.is_import() + || sym.st_value == 0 + || sym.st_name == 0 + { + continue; + } + let name = elf + .dynstrtab + .get_at(sym.st_name) + .unwrap_or(""); + if name.is_empty() { + continue; + } + exports.push(ExportEntry { + name: Some(name.to_string()), + address: sym.st_value, + ordinal: None, + is_forwarded: false, + forward_target: None, + }); + } + + (imports, exports, libraries) +} + +fn extract_pe( + pe: &goblin::pe::PE, +) -> (Vec, Vec, Vec) { + let mut lib_set = HashSet::new(); + let mut imports = Vec::new(); + + for import in &pe.imports { + let dll = import.dll.to_string(); + lib_set.insert(dll.clone()); + let (is_suspicious, threat_tags) = + flag_suspicious(&import.name); + imports.push(ImportEntry { + library: dll, + function: import.name.to_string(), + address: Some(import.rva as u64), + ordinal: Some(import.ordinal), + is_suspicious, + threat_tags, + }); + } + + let mut exports = Vec::new(); + for export in &pe.exports { + let is_forwarded = export.reexport.is_some(); + let forward_target = + export.reexport.as_ref().map(|r| match r { + goblin::pe::export::Reexport::DLLName { + export: name, + lib, + } => format!("{lib}!{name}"), + goblin::pe::export::Reexport::DLLOrdinal { + ordinal, + lib, + } => format!("{lib}!#{ordinal}"), + }); + exports.push(ExportEntry { + name: export.name.map(|s| s.to_string()), + address: export.rva as u64, + ordinal: None, + is_forwarded, + forward_target, + }); + } + + let libraries: Vec = + lib_set.into_iter().collect(); + (imports, exports, libraries) +} + +fn extract_mach( + mach: &goblin::mach::Mach, + data: &[u8], +) -> Result< + (Vec, Vec, Vec), + EngineError, +> { + match mach { + goblin::mach::Mach::Binary(macho) => { + Ok(extract_single_macho(macho)) + } + goblin::mach::Mach::Fat(fat) => { + for arch in fat.iter_arches() { + let arch = arch.map_err(|e| { + EngineError::InvalidBinary { + reason: e.to_string(), + } + })?; + let macho = goblin::mach::MachO::parse( + data, + arch.offset as usize, + ) + .map_err(|e| { + EngineError::InvalidBinary { + reason: e.to_string(), + } + })?; + return Ok(extract_single_macho(&macho)); + } + Ok((Vec::new(), Vec::new(), Vec::new())) + } + } +} + +fn extract_single_macho( + macho: &goblin::mach::MachO, +) -> (Vec, Vec, Vec) { + let mut imports = Vec::new(); + if let Ok(macho_imports) = macho.imports() { + for imp in &macho_imports { + let (is_suspicious, threat_tags) = + flag_suspicious(imp.name); + imports.push(ImportEntry { + library: imp.dylib.to_string(), + function: imp.name.to_string(), + address: Some(imp.address), + ordinal: None, + is_suspicious, + threat_tags, + }); + } + } + + let mut exports = Vec::new(); + if let Ok(macho_exports) = macho.exports() { + for exp in &macho_exports { + exports.push(ExportEntry { + name: Some(exp.name.clone()), + address: exp.offset, + ordinal: None, + is_forwarded: false, + forward_target: None, + }); + } + } + + let libraries: Vec = macho + .libs + .iter() + .filter(|lib| !lib.is_empty()) + .map(|lib| lib.to_string()) + .collect(); + + (imports, exports, libraries) +} + +fn flag_suspicious( + name: &str, +) -> (bool, Vec) { + let mut tags = Vec::new(); + for api in SUSPICIOUS_APIS { + if matches_api(name, api.name) { + tags.push(api.tag.to_string()); + } + } + let is_suspicious = !tags.is_empty(); + (is_suspicious, tags) +} + +fn matches_api( + import_name: &str, + api_name: &str, +) -> bool { + if import_name == api_name { + return true; + } + if import_name.starts_with(api_name) { + let suffix = &import_name[api_name.len()..]; + return suffix == "A" || suffix == "W"; + } + false +} + +fn matches_pattern( + import_name: &str, + pattern: &str, +) -> bool { + if let Some(prefix) = pattern.strip_suffix('*') { + import_name.starts_with(prefix) + } else { + matches_api(import_name, pattern) + } +} + +fn detect_combinations( + imports: &[ImportEntry], +) -> Vec { + let function_names: Vec<&str> = imports + .iter() + .map(|i| i.function.as_str()) + .collect(); + let mut results = Vec::new(); + let mut seen = HashSet::new(); + + for combo in SUSPICIOUS_COMBINATIONS { + if seen.contains(combo.name) { + continue; + } + let all_matched = + combo.patterns.iter().all(|pattern| { + function_names.iter().any(|name| { + matches_pattern(name, pattern) + }) + }); + if !all_matched { + continue; + } + + let matched_apis: Vec = combo + .patterns + .iter() + .filter_map(|pattern| { + function_names + .iter() + .find(|name| { + matches_pattern(name, pattern) + }) + .map(|name| name.to_string()) + }) + .collect(); + + results.push(SuspiciousCombination { + name: combo.name.into(), + description: combo.description.into(), + apis: matched_apis, + mitre_id: combo.mitre_id.into(), + severity: combo.severity.clone(), + }); + seen.insert(combo.name); + } + + results +} + +fn collect_mitre_mappings( + imports: &[ImportEntry], +) -> Vec { + let mut mappings = Vec::new(); + for import in imports { + if !import.is_suspicious { + continue; + } + for api in SUSPICIOUS_APIS { + if matches_api(&import.function, api.name) + && !api.mitre_id.is_empty() + { + mappings.push(MitreMapping { + technique_id: api.mitre_id.into(), + api: import.function.clone(), + tag: api.tag.into(), + }); + } + } + } + mappings +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use super::*; + use crate::context::BinarySource; + + fn load_fixture(name: &str) -> Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path).unwrap_or_else(|e| { + panic!("fixture {path}: {e}") + }) + } + + fn make_ctx(data: Vec) -> AnalysisContext { + let size = data.len() as u64; + AnalysisContext::new( + BinarySource::Buffered(Arc::from(data)), + "deadbeef".into(), + "test.bin".into(), + size, + ) + } + + #[test] + fn elf_imports_extracted() { + let data = load_fixture("hello_elf"); + let result = + analyze_imports(&data).unwrap(); + + assert!( + !result.imports.is_empty(), + "ELF binary should have imports" + ); + assert!( + !result.libraries.is_empty(), + "ELF binary should list needed libraries" + ); + assert!(result.statistics.total_imports > 0); + } + + #[test] + fn suspicious_api_flagging() { + let (is_suspicious, tags) = + flag_suspicious("VirtualAllocEx"); + assert!(is_suspicious); + assert!(tags.contains(&"injection".to_string())); + + let (is_suspicious, tags) = + flag_suspicious("RegSetValueExW"); + assert!(is_suspicious); + assert!(tags.contains( + &"persistence".to_string() + )); + + let (is_suspicious, _) = + flag_suspicious("printf"); + assert!(!is_suspicious); + } + + #[test] + fn combination_detection_injection_chain() { + let imports = vec![ + ImportEntry { + library: "kernel32.dll".into(), + function: "VirtualAllocEx".into(), + address: None, + ordinal: None, + is_suspicious: true, + threat_tags: vec![ + "injection".into(), + ], + }, + ImportEntry { + library: "kernel32.dll".into(), + function: "WriteProcessMemory".into(), + address: None, + ordinal: None, + is_suspicious: true, + threat_tags: vec![ + "injection".into(), + ], + }, + ImportEntry { + library: "kernel32.dll".into(), + function: "CreateRemoteThread".into(), + address: None, + ordinal: None, + is_suspicious: true, + threat_tags: vec![ + "injection".into(), + ], + }, + ]; + + let combos = detect_combinations(&imports); + assert_eq!(combos.len(), 1); + assert_eq!( + combos[0].name, + "Process Injection Chain" + ); + assert_eq!(combos[0].mitre_id, "T1055"); + assert_eq!( + combos[0].severity, + Severity::Critical + ); + } + + #[test] + fn combination_with_aw_suffix() { + let imports = vec![ + ImportEntry { + library: "advapi32.dll".into(), + function: "RegOpenKeyExA".into(), + address: None, + ordinal: None, + is_suspicious: false, + threat_tags: vec![], + }, + ImportEntry { + library: "advapi32.dll".into(), + function: "RegSetValueExW".into(), + address: None, + ordinal: None, + is_suspicious: true, + threat_tags: vec![ + "persistence".into(), + ], + }, + ]; + + let combos = detect_combinations(&imports); + assert!(combos + .iter() + .any(|c| c.name + == "Registry Persistence")); + } + + #[test] + fn no_false_positive_combinations() { + let imports = vec![ImportEntry { + library: "kernel32.dll".into(), + function: "VirtualAllocEx".into(), + address: None, + ordinal: None, + is_suspicious: true, + threat_tags: vec!["injection".into()], + }]; + + let combos = detect_combinations(&imports); + assert!( + !combos.iter().any(|c| c.name + == "Process Injection Chain"), + "should not detect chain with only one API" + ); + } + + #[test] + fn mitre_mappings_collected() { + let imports = vec![ + ImportEntry { + library: String::new(), + function: "ptrace".into(), + address: None, + ordinal: None, + is_suspicious: true, + threat_tags: vec![ + "injection".into(), + ], + }, + ImportEntry { + library: String::new(), + function: "printf".into(), + address: None, + ordinal: None, + is_suspicious: false, + threat_tags: vec![], + }, + ]; + + let mappings = + collect_mitre_mappings(&imports); + assert_eq!(mappings.len(), 1); + assert_eq!( + mappings[0].technique_id, + "T1055.008" + ); + assert_eq!(mappings[0].api, "ptrace"); + } + + #[test] + fn import_pass_populates_context() { + let data = load_fixture("hello_elf"); + let mut ctx = make_ctx(data); + assert!(ctx.import_result.is_none()); + + ImportPass.run(&mut ctx).unwrap(); + assert!(ctx.import_result.is_some()); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/mod.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/mod.rs new file mode 100644 index 0000000..cee2788 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/mod.rs @@ -0,0 +1,9 @@ +// ©AngelaMos | 2026 +// mod.rs + +pub mod disasm; +pub mod entropy; +pub mod format; +pub mod imports; +pub mod strings; +pub mod threat; diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/strings.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/strings.rs new file mode 100644 index 0000000..71479d5 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/strings.rs @@ -0,0 +1,897 @@ +// ©AngelaMos | 2026 +// strings.rs + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::context::AnalysisContext; +use crate::error::EngineError; +use crate::formats::SectionInfo; +use crate::pass::{AnalysisPass, Sealed}; +use crate::passes::imports::SUSPICIOUS_APIS; +use crate::types::{StringCategory, StringEncoding}; + +const MIN_STRING_LENGTH: usize = 4; + +const PRINTABLE_MIN: u8 = 0x20; +const PRINTABLE_MAX: u8 = 0x7E; +const TAB: u8 = 0x09; +const NEWLINE: u8 = 0x0A; +const CARRIAGE_RETURN: u8 = 0x0D; + +const SUSPICIOUS_CATEGORIES: &[StringCategory] = &[ + StringCategory::SuspiciousApi, + StringCategory::PackerSignature, + StringCategory::AntiAnalysis, + StringCategory::PersistencePath, + StringCategory::EncodedData, + StringCategory::ShellCommand, + StringCategory::CryptoWallet, +]; + +const URL_PREFIXES: &[&str] = + &["http://", "https://", "ftp://"]; + +const UNIX_PATH_PREFIXES: &[&str] = &[ + "/etc/", "/tmp/", "/var/", "/bin/", "/usr/", + "/dev/", "/proc/", "/sys/", "/opt/", "/home/", + "/root/", "/lib/", "/sbin/", +]; + +const REGISTRY_PREFIXES: &[&str] = &[ + "HKEY_", + "HKLM\\", + "HKCU\\", + "HKCR\\", + "HKCC\\", + "HKU\\", +]; + +const SHELL_INDICATORS: &[&str] = &[ + "cmd.exe", + "cmd /c", + "cmd /k", + "powershell", + "pwsh", + "/bin/sh", + "/bin/bash", + "/bin/zsh", + "bash -c", + "sh -c", + "| bash", + "|bash", + "| /bin/sh", + "|/bin/sh", + "| /bin/bash", + "|/bin/bash", +]; + +const PACKER_SIGNATURES: &[&str] = &[ + "UPX!", "MPRESS", ".themida", ".vmp", ".enigma", + "PEC2", "ASPack", "MEW ", +]; + +const DEBUG_ARTIFACTS: &[&str] = &[ + "/rustc/", + ".cargo/registry/", + "panicked at", + ".pdb", + "_ZN", + ".debug_", + "DWARF", +]; + +const ANTI_ANALYSIS_INDICATORS: &[&str] = &[ + "VMware", + "VirtualBox", + "VBox", + "QEMU", + "sandbox", + "Sandboxie", + "wireshark", + "procmon", + "x64dbg", + "x32dbg", + "ollydbg", + "IDA Pro", + "Ghidra", + "Immunity", + "SbieDll", + "dbghelp", + "wine_get_unix_file_name", + "TracerPid", + "/proc/self/status", + "/proc/self/maps", +]; + +const PERSISTENCE_PATHS: &[&str] = &[ + "CurrentVersion\\Run", + "CurrentVersion\\RunOnce", + "CurrentVersion\\RunServices", + "/etc/cron", + "/etc/init.d/", + "/etc/systemd/", + ".bashrc", + ".bash_profile", + ".profile", + "/etc/rc.local", + "crontab", + "launchd", + "LaunchAgents", + "LaunchDaemons", + ".config/autostart", +]; + +const BASE64_CHARS: &[u8] = + b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +const BASE64_MIN_LENGTH: usize = 20; + +const IP_OCTET_MAX: u32 = 255; +const IP_OCTET_COUNT: usize = 4; + +const BTC_MIN_LENGTH: usize = 26; +const BTC_MAX_LENGTH: usize = 35; +const ETH_ADDRESS_LENGTH: usize = 42; +const ETH_PREFIX: &str = "0x"; +const ETH_HEX_DIGITS: usize = 40; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StringResult { + pub strings: Vec, + pub statistics: StringStatistics, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExtractedString { + pub value: String, + pub offset: u64, + pub encoding: StringEncoding, + pub length: usize, + pub category: StringCategory, + pub is_suspicious: bool, + pub section: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StringStatistics { + pub total: usize, + pub by_encoding: HashMap, + pub by_category: HashMap, + pub suspicious_count: usize, +} + +pub struct StringPass; + +impl Sealed for StringPass {} + +impl AnalysisPass for StringPass { + fn name(&self) -> &'static str { + "strings" + } + + fn dependencies(&self) -> &[&'static str] { + &["format"] + } + + fn run( + &self, + ctx: &mut AnalysisContext, + ) -> Result<(), EngineError> { + let sections = ctx + .format_result + .as_ref() + .map(|fr| fr.sections.as_slice()); + let result = + extract_strings(ctx.data(), sections); + ctx.string_result = Some(result); + Ok(()) + } +} + +fn extract_strings( + data: &[u8], + sections: Option<&[SectionInfo]>, +) -> StringResult { + let mut strings = Vec::new(); + + extract_ascii(data, sections, &mut strings); + extract_utf16le(data, sections, &mut strings); + + let mut by_encoding = HashMap::new(); + let mut by_category = HashMap::new(); + let mut suspicious_count = 0; + + for s in &strings { + *by_encoding + .entry(s.encoding.clone()) + .or_insert(0) += 1; + *by_category + .entry(s.category.clone()) + .or_insert(0) += 1; + if s.is_suspicious { + suspicious_count += 1; + } + } + + let total = strings.len(); + let statistics = StringStatistics { + total, + by_encoding, + by_category, + suspicious_count, + }; + + StringResult { + strings, + statistics, + } +} + +fn is_printable(b: u8) -> bool { + (PRINTABLE_MIN..=PRINTABLE_MAX).contains(&b) + || b == TAB + || b == NEWLINE + || b == CARRIAGE_RETURN +} + +fn extract_ascii( + data: &[u8], + sections: Option<&[SectionInfo]>, + out: &mut Vec, +) { + let mut start = 0; + let mut in_run = false; + + for (i, &byte) in data.iter().enumerate() { + if is_printable(byte) { + if !in_run { + start = i; + in_run = true; + } + } else if in_run { + let len = i - start; + if len >= MIN_STRING_LENGTH { + if let Ok(s) = + std::str::from_utf8(&data[start..i]) + { + let is_multibyte = s + .bytes() + .any(|b| !b.is_ascii()); + let encoding = if is_multibyte { + StringEncoding::Utf8 + } else { + StringEncoding::Ascii + }; + let category = classify(s); + let is_suspicious = + is_category_suspicious(&category); + let section = sections.and_then(|secs| { + find_section( + secs, + start as u64, + ) + }); + out.push(ExtractedString { + value: s.to_string(), + offset: start as u64, + encoding, + length: len, + category, + is_suspicious, + section, + }); + } + } + in_run = false; + } + } + + if in_run { + let len = data.len() - start; + if len >= MIN_STRING_LENGTH { + if let Ok(s) = + std::str::from_utf8(&data[start..]) + { + let is_multibyte = + s.bytes().any(|b| !b.is_ascii()); + let encoding = if is_multibyte { + StringEncoding::Utf8 + } else { + StringEncoding::Ascii + }; + let category = classify(s); + let is_suspicious = + is_category_suspicious(&category); + let section = sections.and_then(|secs| { + find_section(secs, start as u64) + }); + out.push(ExtractedString { + value: s.to_string(), + offset: start as u64, + encoding, + length: len, + category, + is_suspicious, + section, + }); + } + } + } +} + +fn extract_utf16le( + data: &[u8], + sections: Option<&[SectionInfo]>, + out: &mut Vec, +) { + let mut i = 0; + while i + 1 < data.len() { + let lo = data[i]; + let hi = data[i + 1]; + + if hi == 0x00 && is_printable(lo) { + let start = i; + let mut code_units = Vec::new(); + let mut pos = i; + + while pos + 1 < data.len() { + let clo = data[pos]; + let chi = data[pos + 1]; + if chi == 0x00 && clo == 0x00 { + break; + } + if chi == 0x00 && is_printable(clo) { + code_units.push(u16::from(clo)); + pos += 2; + } else { + break; + } + } + + if code_units.len() >= MIN_STRING_LENGTH { + let value = + String::from_utf16_lossy(&code_units); + let category = classify(&value); + let is_suspicious = + is_category_suspicious(&category); + let section = sections.and_then(|secs| { + find_section(secs, start as u64) + }); + out.push(ExtractedString { + value, + offset: start as u64, + encoding: StringEncoding::Utf16Le, + length: code_units.len(), + category, + is_suspicious, + section, + }); + } + + i = if pos > i { pos } else { i + 2 }; + } else { + i += 2; + } + } +} + +fn find_section( + sections: &[SectionInfo], + file_offset: u64, +) -> Option { + sections + .iter() + .find(|s| { + s.raw_size > 0 + && file_offset >= s.raw_offset + && file_offset + < s.raw_offset + s.raw_size + }) + .map(|s| s.name.clone()) +} + +fn classify(s: &str) -> StringCategory { + if is_url(s) { + return StringCategory::Url; + } + if is_ip_address(s) { + return StringCategory::IpAddress; + } + if is_registry_key(s) { + return StringCategory::RegistryKey; + } + if is_shell_command(s) { + return StringCategory::ShellCommand; + } + if is_persistence_path(s) { + return StringCategory::PersistencePath; + } + if is_anti_analysis(s) { + return StringCategory::AntiAnalysis; + } + if is_packer_signature(s) { + return StringCategory::PackerSignature; + } + if is_suspicious_api(s) { + return StringCategory::SuspiciousApi; + } + if is_debug_artifact(s) { + return StringCategory::DebugArtifact; + } + if is_file_path(s) { + return StringCategory::FilePath; + } + if is_crypto_wallet(s) { + return StringCategory::CryptoWallet; + } + if is_email(s) { + return StringCategory::Email; + } + if is_encoded_data(s) { + return StringCategory::EncodedData; + } + StringCategory::Generic +} + +fn is_category_suspicious( + category: &StringCategory, +) -> bool { + SUSPICIOUS_CATEGORIES.contains(category) +} + +fn is_url(s: &str) -> bool { + let lower = s.to_ascii_lowercase(); + URL_PREFIXES + .iter() + .any(|prefix| lower.starts_with(prefix)) +} + +fn is_ip_address(s: &str) -> bool { + let parts: Vec<&str> = s.split('.').collect(); + if parts.len() != IP_OCTET_COUNT { + return false; + } + parts.iter().all(|part| { + if part.is_empty() || part.len() > 3 { + return false; + } + match part.parse::() { + Ok(val) => val <= IP_OCTET_MAX, + Err(_) => false, + } + }) +} + +fn is_file_path(s: &str) -> bool { + if s.len() >= 3 + && s.as_bytes()[0].is_ascii_uppercase() + && s.as_bytes()[1] == b':' + && s.as_bytes()[2] == b'\\' + { + return true; + } + if s.starts_with("\\\\") { + return true; + } + UNIX_PATH_PREFIXES + .iter() + .any(|prefix| s.starts_with(prefix)) +} + +fn is_registry_key(s: &str) -> bool { + REGISTRY_PREFIXES + .iter() + .any(|prefix| s.starts_with(prefix)) +} + +fn is_shell_command(s: &str) -> bool { + let lower = s.to_ascii_lowercase(); + SHELL_INDICATORS + .iter() + .any(|indicator| lower.contains(indicator)) +} + +fn is_crypto_wallet(s: &str) -> bool { + if s.len() >= BTC_MIN_LENGTH + && s.len() <= BTC_MAX_LENGTH + && (s.starts_with('1') || s.starts_with('3')) + && s.chars() + .all(|c| c.is_ascii_alphanumeric()) + && !s.chars().any(|c| { + c == '0' || c == 'O' || c == 'I' || c == 'l' + }) + { + return true; + } + + if s.len() == ETH_ADDRESS_LENGTH + && s.starts_with(ETH_PREFIX) + && s[2..].len() == ETH_HEX_DIGITS + && s[2..].chars().all(|c| c.is_ascii_hexdigit()) + { + return true; + } + + false +} + +fn is_email(s: &str) -> bool { + let at_pos = match s.find('@') { + Some(p) if p > 0 => p, + _ => return false, + }; + let domain = &s[at_pos + 1..]; + let dot_pos = match domain.rfind('.') { + Some(p) if p > 0 => p, + _ => return false, + }; + let tld = &domain[dot_pos + 1..]; + if tld.len() < 2 { + return false; + } + let local = &s[..at_pos]; + local + .chars() + .all(|c| c.is_ascii_alphanumeric() + || c == '.' + || c == '_' + || c == '%' + || c == '+' + || c == '-') + && domain[..dot_pos] + .chars() + .all(|c| c.is_ascii_alphanumeric() + || c == '.' + || c == '-') + && tld.chars().all(|c| c.is_ascii_alphabetic()) +} + +fn is_suspicious_api(s: &str) -> bool { + SUSPICIOUS_APIS + .iter() + .any(|api| s == api.name) +} + +fn is_packer_signature(s: &str) -> bool { + PACKER_SIGNATURES + .iter() + .any(|sig| s.contains(sig)) +} + +fn is_debug_artifact(s: &str) -> bool { + DEBUG_ARTIFACTS + .iter() + .any(|artifact| s.contains(artifact)) +} + +fn is_anti_analysis(s: &str) -> bool { + let lower = s.to_ascii_lowercase(); + ANTI_ANALYSIS_INDICATORS.iter().any(|indicator| { + lower.contains(&indicator.to_ascii_lowercase()) + }) +} + +fn is_persistence_path(s: &str) -> bool { + PERSISTENCE_PATHS + .iter() + .any(|path| s.contains(path)) +} + +fn is_encoded_data(s: &str) -> bool { + if s.len() < BASE64_MIN_LENGTH { + return false; + } + let trimmed = s.trim_end_matches('='); + if trimmed.is_empty() { + return false; + } + let all_base64 = trimmed + .bytes() + .all(|b| BASE64_CHARS.contains(&b)); + if !all_base64 { + return false; + } + let padding = s.len() - trimmed.len(); + padding <= 2 +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use super::*; + use crate::context::BinarySource; + + fn load_fixture(name: &str) -> Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path).unwrap_or_else(|e| { + panic!("fixture {path}: {e}") + }) + } + + fn make_ctx(data: Vec) -> AnalysisContext { + let size = data.len() as u64; + AnalysisContext::new( + BinarySource::Buffered(Arc::from(data)), + "deadbeef".into(), + "test.bin".into(), + size, + ) + } + + #[test] + fn ascii_extraction_min_length() { + let data = + b"abc\x00abcdef\x00ab\x00longstring\x00"; + let result = extract_strings(data, None); + + let values: Vec<&str> = result + .strings + .iter() + .map(|s| s.value.as_str()) + .collect(); + assert!( + !values.contains(&"abc"), + "3-char string should be excluded" + ); + assert!( + !values.contains(&"ab"), + "2-char string should be excluded" + ); + assert!( + values.contains(&"abcdef"), + "6-char string should be included" + ); + assert!( + values.contains(&"longstring"), + "10-char string should be included" + ); + } + + #[test] + fn utf16le_extraction() { + let s = "Hello World!"; + let mut data: Vec = Vec::new(); + for c in s.encode_utf16() { + data.extend_from_slice(&c.to_le_bytes()); + } + data.push(0x00); + data.push(0x00); + + let result = extract_strings(&data, None); + let utf16_strings: Vec<&ExtractedString> = + result + .strings + .iter() + .filter(|s| { + s.encoding == StringEncoding::Utf16Le + }) + .collect(); + assert!( + !utf16_strings.is_empty(), + "should extract UTF-16LE string" + ); + assert!(utf16_strings + .iter() + .any(|s| s.value.contains("Hello"))); + } + + #[test] + fn category_url() { + assert_eq!( + classify("https://evil.com/payload"), + StringCategory::Url, + ); + assert_eq!( + classify("http://malware.ru/dropper"), + StringCategory::Url, + ); + assert_eq!( + classify("ftp://files.example.com"), + StringCategory::Url, + ); + } + + #[test] + fn category_ip_address() { + assert_eq!( + classify("192.168.1.1"), + StringCategory::IpAddress, + ); + assert_eq!( + classify("10.0.0.1"), + StringCategory::IpAddress, + ); + assert_ne!( + classify("999.999.999.999"), + StringCategory::IpAddress, + ); + assert_ne!( + classify("1.2.3"), + StringCategory::IpAddress, + ); + } + + #[test] + fn category_registry_key() { + assert_eq!( + classify("HKLM\\Software\\Microsoft"), + StringCategory::RegistryKey, + ); + assert_eq!( + classify("HKEY_LOCAL_MACHINE"), + StringCategory::RegistryKey, + ); + } + + #[test] + fn category_file_path() { + assert_eq!( + classify("C:\\Windows\\System32\\notepad.exe"), + StringCategory::FilePath, + ); + assert_eq!( + classify("/tmp/output.log"), + StringCategory::FilePath, + ); + assert_eq!( + classify("\\\\server\\share"), + StringCategory::FilePath, + ); + } + + #[test] + fn category_shell_command() { + assert_eq!( + classify("cmd.exe /c whoami"), + StringCategory::ShellCommand, + ); + assert_eq!( + classify("/bin/bash -c echo hi"), + StringCategory::ShellCommand, + ); + } + + #[test] + fn category_packer_signature() { + assert_eq!( + classify("UPX!"), + StringCategory::PackerSignature, + ); + assert_eq!( + classify("This is .themida packed"), + StringCategory::PackerSignature, + ); + } + + #[test] + fn category_anti_analysis() { + assert_eq!( + classify("VMware Virtual Platform"), + StringCategory::AntiAnalysis, + ); + assert_eq!( + classify("wireshark.exe"), + StringCategory::AntiAnalysis, + ); + } + + #[test] + fn category_persistence() { + assert_eq!( + classify( + "Software\\Microsoft\\Windows\\\ + CurrentVersion\\Run" + ), + StringCategory::PersistencePath, + ); + assert_eq!( + classify("/etc/crontab"), + StringCategory::PersistencePath, + ); + } + + #[test] + fn category_encoded_data() { + assert_eq!( + classify( + "SGVsbG8gV29ybGQhIFRoaXMgaXM=" + ), + StringCategory::EncodedData, + ); + assert_ne!( + classify("short"), + StringCategory::EncodedData, + ); + } + + #[test] + fn category_email() { + assert_eq!( + classify("user@example.com"), + StringCategory::Email, + ); + } + + #[test] + fn suspicious_flag_by_category() { + assert!(is_category_suspicious( + &StringCategory::ShellCommand + )); + assert!(is_category_suspicious( + &StringCategory::AntiAnalysis + )); + assert!(is_category_suspicious( + &StringCategory::PackerSignature + )); + assert!(is_category_suspicious( + &StringCategory::PersistencePath + )); + assert!(is_category_suspicious( + &StringCategory::EncodedData + )); + assert!(is_category_suspicious( + &StringCategory::CryptoWallet + )); + assert!(is_category_suspicious( + &StringCategory::SuspiciousApi + )); + assert!(!is_category_suspicious( + &StringCategory::Generic + )); + assert!(!is_category_suspicious( + &StringCategory::Url + )); + } + + #[test] + fn elf_strings_extracted() { + let data = load_fixture("hello_elf"); + let result = extract_strings(&data, None); + + assert!( + !result.strings.is_empty(), + "ELF binary should contain strings" + ); + assert!(result.statistics.total > 0); + } + + #[test] + fn string_pass_populates_context() { + let data = load_fixture("hello_elf"); + let mut ctx = make_ctx(data); + assert!(ctx.string_result.is_none()); + + StringPass.run(&mut ctx).unwrap(); + assert!(ctx.string_result.is_some()); + } + + #[test] + fn section_attribution() { + let sections = vec![SectionInfo { + name: ".rodata".into(), + virtual_address: 0, + virtual_size: 100, + raw_offset: 10, + raw_size: 100, + permissions: + crate::types::SectionPermissions { + read: true, + write: false, + execute: false, + }, + sha256: String::new(), + }]; + + let found = find_section(§ions, 50); + assert_eq!(found, Some(".rodata".into())); + + let not_found = find_section(§ions, 200); + assert_eq!(not_found, None); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/threat.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/threat.rs new file mode 100644 index 0000000..531c7c9 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/passes/threat.rs @@ -0,0 +1,1388 @@ +// ©AngelaMos | 2026 +// threat.rs + +use std::collections::HashSet; + +use serde::{Deserialize, Serialize}; + +use crate::context::AnalysisContext; +use crate::error::EngineError; +use crate::formats::{FormatAnomaly, FormatResult}; +use crate::pass::{AnalysisPass, Sealed}; +use crate::passes::entropy::EntropyResult; +use crate::passes::imports::ImportResult; +use crate::passes::strings::StringResult; +use crate::types::{ + EntropyFlag, RiskLevel, StringCategory, +}; +use crate::yara::{YaraMatch, YaraScanner}; + +const IMPORT_MAX: u32 = 20; +const ENTROPY_MAX: u32 = 15; +const PACKING_MAX: u32 = 15; +const STRING_MAX: u32 = 10; +const SECTION_MAX: u32 = 10; +const ENTRY_POINT_MAX: u32 = 10; +const ANTI_ANALYSIS_MAX: u32 = 10; +const YARA_MAX: u32 = 10; + +const INJECTION_CHAIN: u32 = 15; +const HOLLOWING_CHAIN: u32 = 15; +const CREDENTIAL_ACCESS: u32 = 12; +const ANTI_DEBUG_API: u32 = 8; +const REGISTRY_RUN_KEYS: u32 = 7; +const NETWORK_DOWNLOAD: u32 = 6; +const VERY_FEW_IMPORTS: u32 = 5; +const APC_INJECTION: u32 = 8; +const LINUX_PTRACE: u32 = 8; +const LINUX_RWX_MEMORY: u32 = 5; +const LINUX_C2_CONNECTION: u32 = 10; +const LINUX_NETWORK_LISTENER: u32 = 8; +const LINUX_DYNAMIC_LOADING: u32 = 5; +const LINUX_PROCESS_INJECTION: u32 = 15; + +const HIGH_SECTION_ENTROPY: u32 = 6; +const HIGH_SECTION_ENTROPY_CAP: u32 = 2; +const VERY_HIGH_OVERALL_ENTROPY: u32 = 3; +const HIGH_ENTROPY_THRESHOLD: f64 = 7.0; +const VERY_HIGH_ENTROPY_THRESHOLD: f64 = 6.8; + +const PACKER_SECTION_NAME: u32 = 5; +const PACKER_SIGNATURE_MATCH: u32 = 3; +const EMPTY_RAW_WITH_VIRTUAL: u32 = 4; +const HIGH_VR_RATIO: u32 = 3; +const PUSHAD_EP: u32 = 3; +const MODIFIED_UPX: u32 = 5; + +const C2_PATTERN: u32 = 5; +const SUSPICIOUS_COMMANDS: u32 = 3; +const BASE64_PE_HEADER: u32 = 4; +const REGISTRY_PERSISTENCE: u32 = 3; +const CRYPTO_WALLET: u32 = 3; + +const RWX_SECTION: u32 = 5; +const EMPTY_SECTION_NAME: u32 = 3; +const UNUSUAL_SECTION_COUNT: u32 = 2; +const ZERO_SIZE_CODE: u32 = 4; + +const EP_OUTSIDE_TEXT: u32 = 5; +const EP_LAST_SECTION: u32 = 5; +const EP_OUTSIDE_ALL: u32 = 7; +const TLS_CALLBACKS: u32 = 3; + +const IS_DEBUGGER_PRESENT: u32 = 3; +const NT_QUERY_INFO_PROCESS: u32 = 5; +const VM_DETECTION_STRINGS: u32 = 3; +const TIMING_CHECK_APIS: u32 = 3; +const SANDBOX_EVASION: u32 = 3; +const LINUX_PTRACE_CHECK: u32 = 5; +const PROC_SELF_ANALYSIS: u32 = 3; + +const YARA_MALWARE_FAMILY: u32 = 10; +const YARA_PACKER_RULE: u32 = 3; +const YARA_SUSPICIOUS: u32 = 5; + +const FEW_IMPORTS_THRESHOLD: usize = 3; +const MAX_NORMAL_SECTIONS: usize = 15; +const BENIGN_MAX: u32 = 15; +const LOW_MAX: u32 = 35; +const MEDIUM_MAX: u32 = 55; +const HIGH_MAX: u32 = 75; +const SUMMARY_TOP_N: usize = 5; + +const SUSPICIOUS_TLDS: &[&str] = &[ + ".ru", ".cn", ".tk", ".pw", ".cc", ".top", + ".xyz", ".buzz", ".onion", +]; + +const BASE64_MZ_PREFIXES: &[&str] = + &["TVqQ", "TVpQ", "TVoA", "TVpB"]; + +const TIMING_CHECK_FUNCTIONS: &[&str] = &[ + "GetTickCount64", + "GetTickCount", + "QueryPerformanceCounter", + "rdtsc", +]; + +const VM_STRINGS: &[&str] = &[ + "vmware", "virtualbox", "vbox", "qemu", + "hyper-v", "xen", +]; + +const SANDBOX_STRINGS: &[&str] = &[ + "sandbox", "cuckoo", "wireshark", "procmon", + "sandboxie", +]; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ThreatResult { + pub total_score: u32, + pub risk_level: RiskLevel, + pub categories: Vec, + pub mitre_techniques: Vec, + pub yara_matches: Vec, + pub summary: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ScoringCategory { + pub name: String, + pub score: u32, + pub max_score: u32, + pub details: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ScoringDetail { + pub rule: String, + pub points: u32, + pub evidence: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MitreMapping { + pub technique_id: String, + pub technique_name: String, + pub tactic: String, + pub evidence: String, +} + +pub struct ThreatPass; + +impl Sealed for ThreatPass {} + +impl AnalysisPass for ThreatPass { + fn name(&self) -> &'static str { + "threat" + } + + fn dependencies(&self) -> &[&'static str] { + &[ + "format", "imports", "strings", + "entropy", "disasm", + ] + } + + fn run( + &self, + ctx: &mut AnalysisContext, + ) -> Result<(), EngineError> { + let yara_scanner = YaraScanner::new()?; + let yara_matches = + yara_scanner.scan(ctx.data())?; + + let format_result = ctx.format_result.as_ref(); + let import_result = ctx.import_result.as_ref(); + let string_result = ctx.string_result.as_ref(); + let entropy_result = + ctx.entropy_result.as_ref(); + + let result = compute_threat_score( + format_result, + import_result, + string_result, + entropy_result, + &yara_matches, + ); + ctx.threat_result = Some(result); + Ok(()) + } +} + +pub fn compute_threat_score( + format_result: Option<&FormatResult>, + import_result: Option<&ImportResult>, + string_result: Option<&StringResult>, + entropy_result: Option<&EntropyResult>, + yara_matches: &[YaraMatch], +) -> ThreatResult { + let cat_import = + score_imports(import_result, string_result); + let cat_entropy = score_entropy(entropy_result); + let cat_packing = score_packing( + entropy_result, + format_result, + string_result, + ); + let cat_strings = score_strings(string_result); + let cat_sections = score_sections(format_result); + let cat_ep = score_entry_point(format_result); + let cat_anti = score_anti_analysis( + import_result, + string_result, + ); + let cat_yara = score_yara(yara_matches); + + let categories = vec![ + cat_import, + cat_entropy, + cat_packing, + cat_strings, + cat_sections, + cat_ep, + cat_anti, + cat_yara, + ]; + + let total_score: u32 = categories + .iter() + .map(|c| c.score.min(c.max_score)) + .sum(); + + let risk_level = classify_risk(total_score); + + let mut mitre_techniques = Vec::new(); + if let Some(ir) = import_result { + for combo in &ir.suspicious_combinations { + if !combo.mitre_id.is_empty() { + mitre_techniques.push(MitreMapping { + technique_id: combo + .mitre_id + .clone(), + technique_name: combo + .name + .clone(), + tactic: combo + .description + .clone(), + evidence: combo + .apis + .join(" + "), + }); + } + } + for mapping in &ir.mitre_mappings { + mitre_techniques.push(MitreMapping { + technique_id: mapping + .technique_id + .clone(), + technique_name: mapping.tag.clone(), + tactic: mapping.tag.clone(), + evidence: mapping.api.clone(), + }); + } + } + + let mut seen_techniques = HashSet::new(); + mitre_techniques.retain(|t| { + seen_techniques.insert(t.technique_id.clone()) + }); + + let summary = generate_summary( + &categories, + total_score, + &risk_level, + ); + + ThreatResult { + total_score, + risk_level, + categories, + mitre_techniques, + yara_matches: yara_matches.to_vec(), + summary, + } +} + +pub fn classify_risk(score: u32) -> RiskLevel { + match score { + 0..=BENIGN_MAX => RiskLevel::Benign, + 16..=LOW_MAX => RiskLevel::Low, + 36..=MEDIUM_MAX => RiskLevel::Medium, + 56..=HIGH_MAX => RiskLevel::High, + _ => RiskLevel::Critical, + } +} + +fn score_imports( + import_result: Option<&ImportResult>, + string_result: Option<&StringResult>, +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + if let Some(ir) = import_result { + for combo in &ir.suspicious_combinations { + let points = match combo.name.as_str() { + "Process Injection Chain" => { + INJECTION_CHAIN + } + "Process Hollowing" => { + HOLLOWING_CHAIN + } + "Credential Theft" => { + CREDENTIAL_ACCESS + } + "APC Injection" => APC_INJECTION, + "DLL Injection" => ANTI_DEBUG_API, + "Download and Execute" + | "Download and Execute (WinInet)" => { + NETWORK_DOWNLOAD + } + "Registry Persistence" => { + REGISTRY_RUN_KEYS + } + "Service Persistence" => { + REGISTRY_RUN_KEYS + } + "Linux ptrace Injection" => { + LINUX_PTRACE + } + "Linux RWX Memory" => { + LINUX_RWX_MEMORY + } + "Linux C2 Connection" => { + LINUX_C2_CONNECTION + } + "Linux Network Listener" => { + LINUX_NETWORK_LISTENER + } + "Linux Dynamic Loading" => { + LINUX_DYNAMIC_LOADING + } + "Linux Process Injection" => { + LINUX_PROCESS_INJECTION + } + _ => 3, + }; + details.push(ScoringDetail { + rule: format!( + "{} detected", + combo.name + ), + points, + evidence: combo + .apis + .join(" + "), + }); + raw += points; + } + + for imp in &ir.imports { + if imp.is_suspicious + && imp + .threat_tags + .iter() + .any(|t| t == "anti-debug") + { + let pts = if imp.function + == "NtQueryInformationProcess" + { + ANTI_DEBUG_API + } else { + 3 + }; + details.push(ScoringDetail { + rule: format!( + "Anti-debug API: {}", + imp.function + ), + points: pts, + evidence: imp.function.clone(), + }); + raw += pts; + } + } + + if ir.statistics.total_imports + < FEW_IMPORTS_THRESHOLD + && ir.statistics.total_imports > 0 + { + details.push(ScoringDetail { + rule: "Very few imports".into(), + points: VERY_FEW_IMPORTS, + evidence: format!( + "{} imports", + ir.statistics.total_imports + ), + }); + raw += VERY_FEW_IMPORTS; + } + } + + if let Some(sr) = string_result { + let import_names: HashSet<&str> = import_result + .map(|ir| { + ir.imports + .iter() + .map(|i| i.function.as_str()) + .collect() + }) + .unwrap_or_default(); + let string_only_apis = sr + .strings + .iter() + .filter(|s| { + s.category + == StringCategory::SuspiciousApi + && !import_names + .contains(s.value.as_str()) + }) + .count(); + if string_only_apis > 0 { + details.push(ScoringDetail { + rule: "Suspicious API strings (not imported)".into(), + points: 3, + evidence: format!( + "{string_only_apis} API names found in strings only" + ), + }); + raw += 3; + } + } + + ScoringCategory { + name: "Import/API Analysis".into(), + score: raw.min(IMPORT_MAX), + max_score: IMPORT_MAX, + details, + } +} + +fn score_entropy( + entropy_result: Option<&EntropyResult>, +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + if let Some(er) = entropy_result { + let mut high_count = 0u32; + for section in &er.sections { + if section.entropy > HIGH_ENTROPY_THRESHOLD + && high_count < HIGH_SECTION_ENTROPY_CAP + { + details.push(ScoringDetail { + rule: format!( + "High entropy section: {}", + section.name + ), + points: HIGH_SECTION_ENTROPY, + evidence: format!( + "entropy={:.2}", + section.entropy + ), + }); + raw += HIGH_SECTION_ENTROPY; + high_count += 1; + } + } + + if er.overall_entropy + > VERY_HIGH_ENTROPY_THRESHOLD + { + details.push(ScoringDetail { + rule: "Very high overall entropy" + .into(), + points: VERY_HIGH_OVERALL_ENTROPY, + evidence: format!( + "overall={:.2}", + er.overall_entropy + ), + }); + raw += VERY_HIGH_OVERALL_ENTROPY; + } + } + + ScoringCategory { + name: "Entropy Analysis".into(), + score: raw.min(ENTROPY_MAX), + max_score: ENTROPY_MAX, + details, + } +} + +fn score_packing( + entropy_result: Option<&EntropyResult>, + format_result: Option<&FormatResult>, + string_result: Option<&StringResult>, +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + if let Some(er) = entropy_result { + let mut has_packer_section = false; + for section in &er.sections { + if section + .flags + .contains(&EntropyFlag::PackerSectionName) + { + has_packer_section = true; + details.push(ScoringDetail { + rule: "Known packer section name" + .into(), + points: PACKER_SECTION_NAME, + evidence: section.name.clone(), + }); + raw += PACKER_SECTION_NAME; + } + if section + .flags + .contains(&EntropyFlag::EmptyRawData) + { + details.push(ScoringDetail { + rule: + "Empty raw with virtual size" + .into(), + points: EMPTY_RAW_WITH_VIRTUAL, + evidence: format!( + "section={} virtual={}", + section.name, + section.virtual_to_raw_ratio + ), + }); + raw += EMPTY_RAW_WITH_VIRTUAL; + } + if section.flags.contains( + &EntropyFlag::HighVirtualToRawRatio, + ) { + details.push(ScoringDetail { + rule: "High virtual/raw ratio" + .into(), + points: HIGH_VR_RATIO, + evidence: format!( + "section={} ratio={:.1}", + section.name, + section.virtual_to_raw_ratio + ), + }); + raw += HIGH_VR_RATIO; + } + } + + if let Some(packer) = &er.packer_name { + details.push(ScoringDetail { + rule: "Packer signature match".into(), + points: PACKER_SIGNATURE_MATCH, + evidence: packer.clone(), + }); + raw += PACKER_SIGNATURE_MATCH; + } + + let has_pushad = er + .packing_indicators + .iter() + .any(|pi| { + pi.indicator_type == "entry_point" + }); + if has_pushad { + details.push(ScoringDetail { + rule: "PUSHAD at entry point".into(), + points: PUSHAD_EP, + evidence: "0x60 at EP".into(), + }); + raw += PUSHAD_EP; + } + + let has_upx_sections = er + .packing_indicators + .iter() + .any(|pi| { + pi.packer_name.as_deref() + == Some("UPX") + }); + let has_upx_magic = string_result + .map_or(false, |sr| { + sr.strings.iter().any(|s| { + s.value.contains("UPX!") + }) + }); + if has_packer_section + && has_upx_sections + && !has_upx_magic + { + details.push(ScoringDetail { + rule: "Modified UPX".into(), + points: MODIFIED_UPX, + evidence: "UPX sections without UPX! magic".into(), + }); + raw += MODIFIED_UPX; + } + } + + if let Some(fr) = format_result { + let suspicious_section_count = + fr.sections.iter().filter(|s| { + crate::formats::SUSPICIOUS_SECTION_NAMES + .iter() + .any(|&(sus, _)| s.name == sus) + }).count(); + if suspicious_section_count > 0 { + details.push(ScoringDetail { + rule: "Suspicious section names".into(), + points: 3, + evidence: format!( + "{suspicious_section_count} suspicious section names" + ), + }); + raw += 3; + } + } + + ScoringCategory { + name: "Packing Indicators".into(), + score: raw.min(PACKING_MAX), + max_score: PACKING_MAX, + details, + } +} + +fn score_strings( + string_result: Option<&StringResult>, +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + if let Some(sr) = string_result { + let has_suspicious_urls = + sr.strings.iter().any(|s| { + s.category == StringCategory::Url + && SUSPICIOUS_TLDS.iter().any( + |tld| { + s.value + .to_ascii_lowercase() + .contains(tld) + }, + ) + }); + if has_suspicious_urls { + details.push(ScoringDetail { + rule: "C2/malicious URL patterns" + .into(), + points: C2_PATTERN, + evidence: "URL with suspicious TLD" + .into(), + }); + raw += C2_PATTERN; + } + + let has_shell_commands = sr + .strings + .iter() + .any(|s| { + s.category == StringCategory::ShellCommand + }); + if has_shell_commands { + details.push(ScoringDetail { + rule: "Suspicious shell commands" + .into(), + points: SUSPICIOUS_COMMANDS, + evidence: "Shell command strings found" + .into(), + }); + raw += SUSPICIOUS_COMMANDS; + } + + let has_base64_pe = sr.strings.iter().any( + |s| { + s.category == StringCategory::EncodedData + && BASE64_MZ_PREFIXES.iter().any( + |prefix| { + s.value.starts_with(prefix) + }, + ) + }, + ); + if has_base64_pe { + details.push(ScoringDetail { + rule: "Base64-encoded PE header" + .into(), + points: BASE64_PE_HEADER, + evidence: "TVqQ/TVpQ prefix in Base64" + .into(), + }); + raw += BASE64_PE_HEADER; + } + + let has_reg_persistence = sr + .strings + .iter() + .any(|s| { + s.category + == StringCategory::PersistencePath + }); + if has_reg_persistence { + details.push(ScoringDetail { + rule: "Registry persistence paths" + .into(), + points: REGISTRY_PERSISTENCE, + evidence: + "Run/RunOnce registry paths found" + .into(), + }); + raw += REGISTRY_PERSISTENCE; + } + + let has_crypto_wallets = sr + .strings + .iter() + .any(|s| { + s.category + == StringCategory::CryptoWallet + }); + if has_crypto_wallets { + details.push(ScoringDetail { + rule: "Crypto wallet addresses".into(), + points: CRYPTO_WALLET, + evidence: "BTC/ETH address patterns" + .into(), + }); + raw += CRYPTO_WALLET; + } + } + + ScoringCategory { + name: "String Analysis".into(), + score: raw.min(STRING_MAX), + max_score: STRING_MAX, + details, + } +} + +fn score_sections( + format_result: Option<&FormatResult>, +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + if let Some(fr) = format_result { + let has_rwx = fr.anomalies.iter().any(|a| { + matches!( + a, + FormatAnomaly::RwxSection { .. } + ) + }); + if has_rwx { + details.push(ScoringDetail { + rule: "RWX section detected".into(), + points: RWX_SECTION, + evidence: "Read+Write+Execute section" + .into(), + }); + raw += RWX_SECTION; + } + + let has_empty_name = + fr.anomalies.iter().any(|a| { + matches!( + a, + FormatAnomaly::EmptySectionName { + .. + } + ) + }); + if has_empty_name { + details.push(ScoringDetail { + rule: "Empty/null section name".into(), + points: EMPTY_SECTION_NAME, + evidence: + "Section with empty or null name" + .into(), + }); + raw += EMPTY_SECTION_NAME; + } + + let section_count = fr.sections.len(); + if section_count > MAX_NORMAL_SECTIONS + || section_count == 0 + { + details.push(ScoringDetail { + rule: "Unusual section count".into(), + points: UNUSUAL_SECTION_COUNT, + evidence: format!( + "{section_count} sections" + ), + }); + raw += UNUSUAL_SECTION_COUNT; + } + + let has_zero_code = fr.sections.iter().any( + |s| { + (s.name == ".text" || s.name == ".code") + && s.raw_size == 0 + }, + ); + if has_zero_code { + details.push(ScoringDetail { + rule: "Zero-size code section".into(), + points: ZERO_SIZE_CODE, + evidence: + ".text/.code with raw_size == 0" + .into(), + }); + raw += ZERO_SIZE_CODE; + } + } + + ScoringCategory { + name: "Section Anomalies".into(), + score: raw.min(SECTION_MAX), + max_score: SECTION_MAX, + details, + } +} + +fn score_entry_point( + format_result: Option<&FormatResult>, +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + if let Some(fr) = format_result { + let ep = fr.entry_point; + + let ep_section = fr.sections.iter().find(|s| { + ep >= s.virtual_address + && ep < s.virtual_address + + s.virtual_size + }); + + let in_text = fr.sections.iter().any(|s| { + s.name == ".text" + && ep >= s.virtual_address + && ep < s.virtual_address + + s.virtual_size + }); + + if !in_text && ep_section.is_some() { + details.push(ScoringDetail { + rule: "EP outside .text section" + .into(), + points: EP_OUTSIDE_TEXT, + evidence: format!( + "EP=0x{ep:x} not in .text" + ), + }); + raw += EP_OUTSIDE_TEXT; + } + + if ep_section.is_none() && !fr.sections.is_empty() + { + details.push(ScoringDetail { + rule: "EP outside all sections".into(), + points: EP_OUTSIDE_ALL, + evidence: format!( + "EP=0x{ep:x} not in any section" + ), + }); + raw += EP_OUTSIDE_ALL; + } + + if let Some(last) = fr.sections.last() { + if ep >= last.virtual_address + && ep < last.virtual_address + + last.virtual_size + && fr.sections.len() > 1 + { + details.push(ScoringDetail { + rule: "EP in last section".into(), + points: EP_LAST_SECTION, + evidence: format!( + "EP=0x{ep:x} in last section '{}'", + last.name + ), + }); + raw += EP_LAST_SECTION; + } + } + + let has_tls = fr.anomalies.iter().any(|a| { + matches!( + a, + FormatAnomaly::TlsCallbacksPresent { + .. + } + ) + }); + if has_tls { + details.push(ScoringDetail { + rule: "TLS callbacks present".into(), + points: TLS_CALLBACKS, + evidence: "PE TLS callback entries" + .into(), + }); + raw += TLS_CALLBACKS; + } + } + + ScoringCategory { + name: "Entry Point Anomalies".into(), + score: raw.min(ENTRY_POINT_MAX), + max_score: ENTRY_POINT_MAX, + details, + } +} + +fn score_anti_analysis( + import_result: Option<&ImportResult>, + string_result: Option<&StringResult>, +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + let all_names = collect_api_and_string_names( + import_result, + string_result, + ); + + if all_names.iter().any(|n| n == "IsDebuggerPresent") + { + details.push(ScoringDetail { + rule: "IsDebuggerPresent detected".into(), + points: IS_DEBUGGER_PRESENT, + evidence: "IsDebuggerPresent".into(), + }); + raw += IS_DEBUGGER_PRESENT; + } + + if all_names + .iter() + .any(|n| n == "NtQueryInformationProcess") + { + details.push(ScoringDetail { + rule: "NtQueryInformationProcess detected" + .into(), + points: NT_QUERY_INFO_PROCESS, + evidence: "NtQueryInformationProcess" + .into(), + }); + raw += NT_QUERY_INFO_PROCESS; + } + + if let Some(sr) = string_result { + let has_vm = sr.strings.iter().any(|s| { + let lower = s.value.to_ascii_lowercase(); + VM_STRINGS + .iter() + .any(|vm| lower.contains(vm)) + }); + if has_vm { + details.push(ScoringDetail { + rule: "VM detection strings".into(), + points: VM_DETECTION_STRINGS, + evidence: + "VMware/VBox/QEMU/Hyper-V strings" + .into(), + }); + raw += VM_DETECTION_STRINGS; + } + + let has_sandbox = sr.strings.iter().any(|s| { + let lower = s.value.to_ascii_lowercase(); + SANDBOX_STRINGS + .iter() + .any(|sb| lower.contains(sb)) + }); + if has_sandbox { + details.push(ScoringDetail { + rule: "Sandbox evasion strings".into(), + points: SANDBOX_EVASION, + evidence: + "sandbox/cuckoo/wireshark strings" + .into(), + }); + raw += SANDBOX_EVASION; + } + + let has_linux_anti_debug = + sr.strings.iter().any(|s| { + s.category + == StringCategory::AntiAnalysis + && s.value.contains("TracerPid") + }); + if has_linux_anti_debug { + details.push(ScoringDetail { + rule: "Linux ptrace anti-debug" + .into(), + points: LINUX_PTRACE_CHECK, + evidence: + "TracerPid check detected" + .into(), + }); + raw += LINUX_PTRACE_CHECK; + } + + let has_proc_analysis = + sr.strings.iter().any(|s| { + s.category + == StringCategory::AntiAnalysis + && (s.value + .contains("/proc/self/maps") + || s.value.contains( + "/proc/self/status", + )) + }); + if has_proc_analysis { + details.push(ScoringDetail { + rule: "/proc/self analysis".into(), + points: PROC_SELF_ANALYSIS, + evidence: + "Process self-inspection detected" + .into(), + }); + raw += PROC_SELF_ANALYSIS; + } + } + + let has_timing = all_names.iter().any(|n| { + TIMING_CHECK_FUNCTIONS + .iter() + .any(|t| n.contains(t)) + }); + if has_timing { + details.push(ScoringDetail { + rule: "Timing check APIs".into(), + points: TIMING_CHECK_APIS, + evidence: "GetTickCount/QueryPerformanceCounter".into(), + }); + raw += TIMING_CHECK_APIS; + } + + ScoringCategory { + name: "Anti-Analysis Indicators".into(), + score: raw.min(ANTI_ANALYSIS_MAX), + max_score: ANTI_ANALYSIS_MAX, + details, + } +} + +fn score_yara( + yara_matches: &[YaraMatch], +) -> ScoringCategory { + let mut details = Vec::new(); + let mut raw = 0u32; + + for ym in yara_matches { + let category = ym + .metadata + .category + .as_deref() + .unwrap_or(""); + let severity = ym + .metadata + .severity + .as_deref() + .unwrap_or(""); + + let points = if category == "malware" + || severity == "critical" + { + YARA_MALWARE_FAMILY + } else if category == "packer" { + YARA_PACKER_RULE + } else if category == "c2" + || category == "credential-access" + { + YARA_SUSPICIOUS + 2 + } else { + YARA_SUSPICIOUS + }; + + details.push(ScoringDetail { + rule: format!( + "YARA: {}", + ym.rule_name + ), + points, + evidence: ym + .metadata + .description + .clone() + .unwrap_or_default(), + }); + raw += points; + } + + ScoringCategory { + name: "YARA Signature Matches".into(), + score: raw.min(YARA_MAX), + max_score: YARA_MAX, + details, + } +} + +fn collect_api_and_string_names( + import_result: Option<&ImportResult>, + string_result: Option<&StringResult>, +) -> Vec { + let mut names = Vec::new(); + if let Some(ir) = import_result { + for imp in &ir.imports { + names.push(imp.function.clone()); + } + } + if let Some(sr) = string_result { + for s in &sr.strings { + if s.category + == StringCategory::SuspiciousApi + { + names.push(s.value.clone()); + } + } + } + names +} + +fn generate_summary( + categories: &[ScoringCategory], + total_score: u32, + risk_level: &RiskLevel, +) -> String { + let mut all_details: Vec<(&str, &ScoringDetail)> = + Vec::new(); + for cat in categories { + for detail in &cat.details { + all_details + .push((&cat.name, detail)); + } + } + all_details + .sort_by(|a, b| b.1.points.cmp(&a.1.points)); + + let top: Vec = all_details + .iter() + .take(SUMMARY_TOP_N) + .map(|(_, d)| d.rule.clone()) + .collect(); + + if top.is_empty() { + return format!( + "{risk_level} risk (score {total_score}/100): \ + No significant threat indicators detected" + ); + } + + format!( + "{risk_level} risk (score {total_score}/100): {}", + top.join(", ") + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn risk_level_classification() { + assert_eq!(classify_risk(0), RiskLevel::Benign); + assert_eq!( + classify_risk(15), + RiskLevel::Benign + ); + assert_eq!(classify_risk(16), RiskLevel::Low); + assert_eq!(classify_risk(35), RiskLevel::Low); + assert_eq!( + classify_risk(36), + RiskLevel::Medium + ); + assert_eq!( + classify_risk(55), + RiskLevel::Medium + ); + assert_eq!(classify_risk(56), RiskLevel::High); + assert_eq!(classify_risk(75), RiskLevel::High); + assert_eq!( + classify_risk(76), + RiskLevel::Critical + ); + assert_eq!( + classify_risk(100), + RiskLevel::Critical + ); + } + + #[test] + fn category_capping() { + let cat = ScoringCategory { + name: "Test".into(), + score: 30, + max_score: 20, + details: Vec::new(), + }; + assert_eq!(cat.score.min(cat.max_score), 20); + } + + #[test] + fn total_is_sum_of_capped() { + let result = compute_threat_score( + None, None, None, None, &[], + ); + assert_eq!(result.total_score, 0); + assert_eq!( + result.risk_level, + RiskLevel::Benign + ); + } + + #[test] + fn summary_empty_when_no_threats() { + let result = compute_threat_score( + None, None, None, None, &[], + ); + assert!(result.summary.contains("BENIGN")); + assert!(result.summary.contains("0/100")); + } + + #[test] + fn yara_scoring_malware() { + let matches = vec![YaraMatch { + rule_name: "test_malware".into(), + tags: Vec::new(), + metadata: crate::yara::YaraMetadata { + description: Some( + "Test malware rule".into(), + ), + category: Some("malware".into()), + severity: Some("critical".into()), + }, + matched_strings: Vec::new(), + }]; + let cat = score_yara(&matches); + assert_eq!(cat.score, YARA_MALWARE_FAMILY); + } + + #[test] + fn yara_scoring_packer() { + let matches = vec![YaraMatch { + rule_name: "test_packer".into(), + tags: Vec::new(), + metadata: crate::yara::YaraMetadata { + description: Some( + "Test packer rule".into(), + ), + category: Some("packer".into()), + severity: Some("medium".into()), + }, + matched_strings: Vec::new(), + }]; + let cat = score_yara(&matches); + assert_eq!(cat.score, YARA_PACKER_RULE); + } + + #[test] + fn entropy_scoring() { + use crate::passes::entropy::{ + EntropyResult, SectionEntropy, + }; + use crate::types::EntropyClassification; + + let er = EntropyResult { + overall_entropy: 7.2, + sections: vec![SectionEntropy { + name: ".text".into(), + entropy: 7.5, + size: 4096, + classification: + EntropyClassification::Encrypted, + virtual_to_raw_ratio: 1.0, + is_anomalous: true, + flags: vec![EntropyFlag::HighEntropy], + }], + packing_detected: false, + packer_name: None, + packing_indicators: Vec::new(), + }; + let cat = score_entropy(Some(&er)); + assert!( + cat.score > 0, + "should score for high entropy" + ); + assert!(cat.details.len() >= 2); + } + + #[test] + fn section_rwx_scoring() { + use crate::formats::{ + FormatResult, SectionInfo, + }; + use crate::types::{ + Architecture, BinaryFormat, Endianness, + SectionPermissions, + }; + + let fr = FormatResult { + format: BinaryFormat::Elf, + architecture: Architecture::X86_64, + bits: 64, + endianness: Endianness::Little, + entry_point: 0x1000, + is_stripped: false, + is_pie: false, + has_debug_info: false, + sections: vec![SectionInfo { + name: ".text".into(), + virtual_address: 0x1000, + virtual_size: 0x1000, + raw_offset: 0, + raw_size: 0x1000, + permissions: SectionPermissions { + read: true, + write: true, + execute: true, + }, + sha256: String::new(), + }], + segments: Vec::new(), + anomalies: vec![ + FormatAnomaly::RwxSection { + name: ".text".into(), + }, + ], + pe_info: None, + elf_info: None, + macho_info: None, + function_hints: Vec::new(), + }; + let cat = score_sections(Some(&fr)); + assert_eq!(cat.score, RWX_SECTION); + } + + #[test] + fn threat_pass_populates_context() { + use std::sync::Arc; + use crate::context::BinarySource; + + fn load_fixture(name: &str) -> Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path).unwrap_or_else( + |e| panic!("fixture {path}: {e}"), + ) + } + + let data = load_fixture("hello_elf"); + let size = data.len() as u64; + let mut ctx = AnalysisContext::new( + BinarySource::Buffered(Arc::from(data)), + "deadbeef".into(), + "test.bin".into(), + size, + ); + + crate::passes::format::FormatPass + .run(&mut ctx) + .unwrap(); + crate::passes::imports::ImportPass + .run(&mut ctx) + .unwrap(); + crate::passes::strings::StringPass + .run(&mut ctx) + .unwrap(); + crate::passes::entropy::EntropyPass + .run(&mut ctx) + .unwrap(); + crate::passes::disasm::DisasmPass + .run(&mut ctx) + .unwrap(); + + assert!(ctx.threat_result.is_none()); + ThreatPass.run(&mut ctx).unwrap(); + assert!(ctx.threat_result.is_some()); + + let result = ctx.threat_result.unwrap(); + assert_eq!(result.categories.len(), 8); + assert!(result.total_score <= 100); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/types.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/types.rs new file mode 100644 index 0000000..61337cb --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/types.rs @@ -0,0 +1,161 @@ +// ©AngelaMos | 2026 +// types.rs + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum BinaryFormat { + Elf, + Pe, + MachO, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Architecture { + X86, + X86_64, + Arm, + Aarch64, + Other(String), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Endianness { + Little, + Big, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum RiskLevel { + Benign, + Low, + Medium, + High, + Critical, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Severity { + Low, + Medium, + High, + Critical, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum StringEncoding { + Ascii, + Utf8, + Utf16Le, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum StringCategory { + Url, + IpAddress, + FilePath, + RegistryKey, + ShellCommand, + CryptoWallet, + Email, + SuspiciousApi, + PackerSignature, + DebugArtifact, + AntiAnalysis, + PersistencePath, + EncodedData, + Generic, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum EntropyClassification { + Plaintext, + NativeCode, + Compressed, + Packed, + Encrypted, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum EntropyFlag { + HighEntropy, + HighVirtualToRawRatio, + EmptyRawData, + Rwx, + PackerSectionName, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum FlowControlType { + Next, + Branch, + ConditionalBranch, + Call, + Return, + Interrupt, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum CfgEdgeType { + Fallthrough, + ConditionalTrue, + ConditionalFalse, + Unconditional, + Call, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SectionPermissions { + pub read: bool, + pub write: bool, + pub execute: bool, +} + +impl std::fmt::Display for BinaryFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Elf => write!(f, "ELF"), + Self::Pe => write!(f, "PE"), + Self::MachO => write!(f, "Mach-O"), + } + } +} + +impl std::fmt::Display for Architecture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::X86 => write!(f, "x86"), + Self::X86_64 => write!(f, "x86_64"), + Self::Arm => write!(f, "ARM"), + Self::Aarch64 => write!(f, "AArch64"), + Self::Other(name) => write!(f, "{name}"), + } + } +} + +impl std::fmt::Display for RiskLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Benign => write!(f, "BENIGN"), + Self::Low => write!(f, "LOW"), + Self::Medium => write!(f, "MEDIUM"), + Self::High => write!(f, "HIGH"), + Self::Critical => write!(f, "CRITICAL"), + } + } +} + +impl std::fmt::Display for Endianness { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Little => write!(f, "Little-endian"), + Self::Big => write!(f, "Big-endian"), + } + } +} + +impl SectionPermissions { + pub fn is_rwx(&self) -> bool { + self.read && self.write && self.execute + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/yara.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/yara.rs new file mode 100644 index 0000000..39232cb --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/src/yara.rs @@ -0,0 +1,493 @@ +// ©AngelaMos | 2026 +// yara.rs + +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +use crate::error::EngineError; + +const BUILTIN_RULES: &str = r#" +rule suspicious_upx_packed { + meta: + description = "Detects UPX packed binaries" + category = "packer" + severity = "medium" + strings: + $upx0 = "UPX0" + $upx1 = "UPX1" + $upx_magic = { 55 50 58 21 } + condition: + ($upx0 and $upx1) or $upx_magic +} + +rule suspicious_anti_debug { + meta: + description = "Detects common anti-debugging techniques" + category = "evasion" + severity = "high" + strings: + $api1 = "IsDebuggerPresent" + $api2 = "CheckRemoteDebuggerPresent" + $api3 = "NtQueryInformationProcess" + $api4 = "OutputDebugString" + $int2d = { CD 2D } + condition: + 2 of ($api*) or $int2d +} + +rule suspicious_process_injection { + meta: + description = "Detects potential process injection capabilities" + category = "injection" + severity = "critical" + strings: + $api1 = "VirtualAllocEx" + $api2 = "WriteProcessMemory" + $api3 = "CreateRemoteThread" + $api4 = "NtUnmapViewOfSection" + condition: + ($api1 and $api2 and $api3) or ($api4 and $api2) +} + +rule suspicious_keylogger { + meta: + description = "Detects potential keylogger behavior" + category = "spyware" + severity = "high" + strings: + $api1 = "GetAsyncKeyState" + $api2 = "SetWindowsHookEx" + $api3 = "GetKeyState" + $api4 = "GetKeyboardState" + condition: + 2 of them +} + +rule suspicious_crypto_mining { + meta: + description = "Detects cryptocurrency mining indicators" + category = "miner" + severity = "medium" + strings: + $pool1 = "stratum+tcp://" + $pool2 = "stratum+ssl://" + $algo1 = "cryptonight" + $algo2 = "randomx" + $algo3 = "ethash" + $wallet = /[13][a-km-zA-HJ-NP-Z1-9]{25,34}/ + condition: + any of ($pool*) or (any of ($algo*) and $wallet) +} + +rule suspicious_persistence { + meta: + description = "Detects Windows persistence mechanisms" + category = "persistence" + severity = "high" + strings: + $reg1 = "CurrentVersion\\Run" + $reg2 = "CurrentVersion\\RunOnce" + $svc1 = "CreateServiceA" + $svc2 = "CreateServiceW" + $task = "schtasks" + condition: + any of them +} + +rule suspicious_network_backdoor { + meta: + description = "Detects potential backdoor network behavior" + category = "backdoor" + severity = "critical" + strings: + $bind = "bind" + $listen = "listen" + $accept = "accept" + $shell1 = "cmd.exe" + $shell2 = "/bin/sh" + $shell3 = "/bin/bash" + condition: + ($bind and $listen and $accept) and any of ($shell*) +} + +rule suspicious_ransomware { + meta: + description = "Detects potential ransomware indicators" + category = "ransomware" + severity = "critical" + strings: + $ext1 = ".encrypted" + $ext2 = ".locked" + $ext3 = ".crypto" + $ransom1 = "your files have been encrypted" + $ransom2 = "bitcoin" + $ransom3 = "decrypt" + $crypto1 = "CryptEncrypt" + $crypto2 = "CryptGenKey" + condition: + (any of ($ext*) and any of ($ransom*)) or + (any of ($crypto*) and any of ($ransom*)) +} + +rule suspicious_shellcode { + meta: + description = "Detects potential shellcode patterns" + category = "shellcode" + severity = "high" + strings: + $nop_sled = { 90 90 90 90 90 90 90 90 } + $egg_hunter1 = { 66 81 CA FF 0F } + $stack_pivot = { 94 C3 } + condition: + any of them +} + +rule suspicious_obfuscation { + meta: + description = "Detects common obfuscation patterns" + category = "obfuscation" + severity = "medium" + strings: + $xor_loop = { 80 3? ?? 74 ?? 80 3? ?? } + $decode_base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + condition: + any of them +} + +rule suspicious_linux_anti_debug { + meta: + description = "Detects Linux anti-debugging via /proc inspection" + category = "evasion" + severity = "high" + strings: + $tracer = "TracerPid" + $proc_status = "/proc/self/status" + $proc_maps = "/proc/self/maps" + condition: + $tracer or ($proc_status and $proc_maps) +} + +rule suspicious_linux_persistence { + meta: + description = "Detects Linux persistence mechanisms" + category = "persistence" + severity = "high" + strings: + $cron1 = "/etc/cron" + $cron2 = "crontab" + $init = "/etc/init.d/" + $systemd = "/etc/systemd/" + $bashrc = ".bashrc" + $profile = ".bash_profile" + $rc_local = "/etc/rc.local" + $xdg_autostart = ".config/autostart" + condition: + 2 of them +} + +rule suspicious_c2_endpoints { + meta: + description = "Detects common C2 server endpoint paths" + category = "c2" + severity = "high" + strings: + $gate = "/gate.php" + $beacon = "/beacon" + $callback = "/callback" + $checkin = "/checkin" + $exfil = "/exfil" + $panel = "/panel/" + $command = "/command" + $bot = "/bot/" + $upload_php = "/upload.php" + condition: + 2 of them +} + +rule suspicious_credential_access { + meta: + description = "Detects credential file access patterns" + category = "credential-access" + severity = "high" + strings: + $passwd = "/etc/passwd" + $shadow = "/etc/shadow" + $ssh_key = ".ssh/id_rsa" + $ssh_key2 = ".ssh/authorized_keys" + $kerberos = "/etc/krb5.conf" + $gnupg = ".gnupg/" + condition: + $shadow or ($passwd and any of ($ssh*, $kerberos, $gnupg)) +} +"#; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct YaraMatch { + pub rule_name: String, + pub tags: Vec, + pub metadata: YaraMetadata, + pub matched_strings: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct YaraMetadata { + pub description: Option, + pub category: Option, + pub severity: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct YaraStringMatch { + pub identifier: String, + pub match_count: usize, +} + +pub struct YaraScanner { + rules: yara_x::Rules, +} + +impl YaraScanner { + pub fn new() -> Result { + let mut compiler = yara_x::Compiler::new(); + compiler.add_source(BUILTIN_RULES).map_err( + |e| EngineError::Yara(e.to_string()), + )?; + + Ok(Self { + rules: compiler.build(), + }) + } + + pub fn with_custom_rules( + rules_dir: &Path, + ) -> Result { + let mut compiler = yara_x::Compiler::new(); + compiler.add_source(BUILTIN_RULES).map_err( + |e| EngineError::Yara(e.to_string()), + )?; + + if rules_dir.is_dir() { + for entry in std::fs::read_dir(rules_dir) + .map_err(|e| { + EngineError::Yara(format!( + "failed to read rules dir: {e}" + )) + })? + { + let entry = entry.map_err(|e| { + EngineError::Yara(format!( + "dir entry error: {e}" + )) + })?; + let path = entry.path(); + if path + .extension() + .is_some_and(|ext| ext == "yar" || ext == "yara") + { + let source = std::fs::read_to_string( + &path, + ) + .map_err(|e| { + EngineError::Yara(format!( + "failed to read {}: {e}", + path.display() + )) + })?; + compiler + .add_source(source.as_str()) + .map_err(|e| { + EngineError::Yara(format!( + "compile error in {}: {e}", + path.display() + )) + })?; + } + } + } + + Ok(Self { + rules: compiler.build(), + }) + } + + pub fn scan( + &self, + data: &[u8], + ) -> Result, EngineError> { + let mut scanner = + yara_x::Scanner::new(&self.rules); + let results = scanner.scan(data).map_err( + |e| EngineError::Yara(e.to_string()), + )?; + + let mut matches = Vec::new(); + for rule in results.matching_rules() { + let tags: Vec = rule + .tags() + .map(|t| t.identifier().to_string()) + .collect(); + + let mut description = None; + let mut category = None; + let mut severity = None; + for (key, value) in rule.metadata() { + match key { + "description" => { + if let yara_x::MetaValue::String(s) = value { + description = + Some(s.to_string()); + } + } + "category" => { + if let yara_x::MetaValue::String(s) = value { + category = + Some(s.to_string()); + } + } + "severity" => { + if let yara_x::MetaValue::String(s) = value { + severity = + Some(s.to_string()); + } + } + _ => {} + } + } + + let mut matched_strings = Vec::new(); + for pattern in rule.patterns() { + let id = pattern + .identifier() + .to_string(); + let count = + pattern.matches().count(); + if count > 0 { + matched_strings.push( + YaraStringMatch { + identifier: id, + match_count: count, + }, + ); + } + } + + matches.push(YaraMatch { + rule_name: rule + .identifier() + .to_string(), + tags, + metadata: YaraMetadata { + description, + category, + severity, + }, + matched_strings, + }); + } + + Ok(matches) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn builtin_rules_compile() { + let scanner = YaraScanner::new().unwrap(); + let result = scanner.scan(&[0u8; 64]).unwrap(); + assert!(result.is_empty() || !result.is_empty()); + } + + #[test] + fn detects_upx_signature() { + let mut data = vec![0u8; 512]; + let upx0 = b"UPX0"; + let upx1 = b"UPX1"; + data[0x100..0x104].copy_from_slice(upx0); + data[0x140..0x144].copy_from_slice(upx1); + + let scanner = YaraScanner::new().unwrap(); + let result = scanner.scan(&data).unwrap(); + let upx_match = result + .iter() + .find(|m| m.rule_name == "suspicious_upx_packed"); + assert!( + upx_match.is_some(), + "should detect UPX packer signature" + ); + let meta = + &upx_match.unwrap().metadata; + assert_eq!( + meta.category.as_deref(), + Some("packer") + ); + } + + #[test] + fn detects_process_injection() { + let mut data = Vec::new(); + data.extend_from_slice( + b"\x00\x00VirtualAllocEx\x00\x00", + ); + data.extend_from_slice( + b"\x00\x00WriteProcessMemory\x00\x00", + ); + data.extend_from_slice( + b"\x00\x00CreateRemoteThread\x00\x00", + ); + data.extend_from_slice(&[0u8; 256]); + + let scanner = YaraScanner::new().unwrap(); + let result = scanner.scan(&data).unwrap(); + let injection = result.iter().find(|m| { + m.rule_name + == "suspicious_process_injection" + }); + assert!( + injection.is_some(), + "should detect process injection APIs" + ); + } + + #[test] + fn clean_data_no_matches() { + let data = b"Hello, this is perfectly normal text content with nothing suspicious at all."; + let scanner = YaraScanner::new().unwrap(); + let result = scanner.scan(data).unwrap(); + let suspicious: Vec<_> = result + .iter() + .filter(|m| { + m.rule_name != "suspicious_obfuscation" + }) + .collect(); + assert!( + suspicious.is_empty(), + "clean text should not trigger suspicious rules, got: {:?}", + suspicious.iter().map(|m| &m.rule_name).collect::>() + ); + } + + fn load_fixture(name: &str) -> Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path).unwrap_or_else(|e| { + panic!("fixture {path}: {e}") + }) + } + + #[test] + fn scan_elf_binary() { + let data = load_fixture("hello_elf"); + let scanner = YaraScanner::new().unwrap(); + let result = scanner.scan(&data).unwrap(); + assert!( + result.is_empty() || !result.is_empty(), + "scan should complete without error" + ); + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/tests/fixtures/hello_elf b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/tests/fixtures/hello_elf new file mode 100755 index 0000000000000000000000000000000000000000..6d827e1c5688ede68a613c8c3e68d59482662003 GIT binary patch literal 15832 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 literal 0 HcmV?d00001 diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/tests/fixtures/hello_elf_stripped b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem-engine/tests/fixtures/hello_elf_stripped new file mode 100755 index 0000000000000000000000000000000000000000..e1d7ac0b96af09607db9f68b32e8e3db89f36065 GIT binary patch literal 14384 zcmeHOU2GIp6u#S1SpK>zf@0gyjVTEz-L_CFh=qmnvsEAn_(o9iT9G3@1CFU zo-=3eOqrcK$9L`BT@eZ?v`V#Av6P52JjD21N}U% zjf#uI*4Y@X@Z^<(;IYWOsPLk~1D=&Ufv6|fgtr4y&mhLg#x3UVl?w6(gcrmH@dt7Y zZj8Kr8lxSxAjVwn-FV;~x@o7mAIuMOkcT`E2ydP^1aY0j10vqj#BBfwe8yspJ+SJzA)*3J7^#;d#WmbFaQmWv-w*o;0~0F)w5HdHQfbKPdx-6Ji%S-@KR$rlgq&`8tGt}!n1&5YzI>)a+n;icv zIr_!)fv#OG6D=PmPqtqryR#xiuD5&Rk@^>1cjlAr4bmo0R=vis`HsDU!f)k1gSbdFWjk2hAt(q&)CapfTc1(*qQ8&)bG5@|1q-a zw~_YMWLATZ9!`$7f6Wip$?==^lH`f@`J|s(>p0F-ns%3}{v>1Q2*obNXZ}}5$#|GJ z=Q*<@7u}l8suJsU(-qJa&=t@X&=t@X&=t@X&=t@X&=t@X&=vUaR)F7sg=#Cetf3V7 z{ZZYtyEFfLHCL&3x&pcax&pcax&pcax&pcax&pca zx&r@$3Sj*r)+=HyBA(%vpa!y@ajD3yvhMLA*k%1=t;l!=tAQ+ben#X5`%g1Yp5?Q$ zs1a)w`}yIGnzzPPsT*QnltbcADLmk&Cso z(i+_6#+J=%rAy1%i~U8rU>n^kp3B>2ye~HxA1LGp%z{1aQ*{qow0zl0tGJb;wYme! zb?i5a{VJXw&J~BV9@+)Z^1N9nTKU{8Aw{-=nK78a<5;$e4`l3kU!FR)IYfI}JZzCu zK5f{BiktnZ-hz=eQ~hb8LXxbu@91o_jXrOnlu;-clrQtqrzl3&>Y)_!Hpig^-BG)X zMHTPKXR~I`E?2#K*YkYkIf&mNuiz@%bJA+54!3ltO5xF1KL*o>nQoIq*3FANL{Pgg7F8 z@c4(x&c}rNqWc3EbL9T~AoqPOc>cd6o1Y&O9#F*62#|rt1N>$&0Ff^SfD9Z9@DsuZ z-Ud++aiM-Cz{lSqAnxDr4<7$%;g`OFh>53Q&>u38e-q{U;rz`5PmsSG@HgD%!5(la{a)^cN7y%q@=dSgVG*+;OAuV^YQ Vec { + let path = format!( + "{}/tests/fixtures/{name}", + env!("CARGO_MANIFEST_DIR"), + ); + std::fs::read(&path) + .unwrap_or_else(|e| panic!("fixture {path}: {e}")) +} + +#[test] +fn full_pipeline_elf() { + let engine = AnalysisEngine::new().unwrap(); + let data = load_fixture("hello_elf"); + let (ctx, report) = + engine.analyze(&data, "hello_elf"); + + assert!( + report.all_succeeded(), + "all passes should succeed: {:?}", + report + .failed_passes() + .iter() + .map(|p| (p.name, p.error_message.as_deref())) + .collect::>() + ); + + let fmt = ctx.format_result.as_ref().unwrap(); + assert_eq!(fmt.format, BinaryFormat::Elf); + assert!(!fmt.sections.is_empty()); + + assert!(ctx.import_result.is_some()); + assert!(ctx.string_result.is_some()); + assert!(ctx.entropy_result.is_some()); + assert!(ctx.disassembly_result.is_some()); + + let disasm = + ctx.disassembly_result.as_ref().unwrap(); + assert!(disasm.total_functions > 0); + assert!(disasm.total_instructions > 0); + + let threat = ctx.threat_result.as_ref().unwrap(); + assert!(threat.total_score <= 100); + assert_eq!(threat.categories.len(), 8); + assert!(!threat.summary.is_empty()); +} + +#[test] +fn full_pipeline_stripped_elf() { + let engine = AnalysisEngine::new().unwrap(); + let data = load_fixture("hello_elf_stripped"); + let (ctx, report) = + engine.analyze(&data, "hello_elf_stripped"); + + assert!(report.all_succeeded()); + + let fmt = ctx.format_result.as_ref().unwrap(); + assert!(fmt.is_stripped); + + assert!(ctx.threat_result.is_some()); +} + +#[test] +fn sha256_computed_correctly() { + let engine = AnalysisEngine::new().unwrap(); + let data = load_fixture("hello_elf"); + let (ctx, _) = engine.analyze(&data, "test.bin"); + + assert_eq!(ctx.sha256.len(), 64); + assert!(ctx.sha256.chars().all(|c| c.is_ascii_hexdigit())); +} + +#[test] +fn invalid_binary_handled() { + let engine = AnalysisEngine::new().unwrap(); + let data = vec![0xDE, 0xAD, 0xBE, 0xEF]; + let (_, report) = + engine.analyze(&data, "garbage.bin"); + + assert!( + !report.all_succeeded(), + "invalid binary should cause format pass failure" + ); + assert!(report + .failed_passes() + .iter() + .any(|p| p.name == "format")); +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/Cargo.toml b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/Cargo.toml new file mode 100644 index 0000000..eff62dd --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/Cargo.toml @@ -0,0 +1,31 @@ +# ©AngelaMos | 2026 +# Cargo.toml + +[package] +name = "axumortem" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +axumortem-engine = { path = "../axumortem-engine" } +axum = { version = "0.8", features = ["multipart"] } +tokio = { workspace = true } +sqlx = { version = "0.8", features = [ + "runtime-tokio", + "postgres", + "uuid", + "chrono", + "json", + "migrate", +] } +tower = "0.5" +tower-http = { version = "0.6", features = ["cors", "trace", "limit"] } +uuid = { version = "1", features = ["v4", "serde"] } +chrono = { version = "0.4", features = ["serde"] } +clap = { version = "4", features = ["derive", "env"] } +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +anyhow = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/migrations/001_initial.sql b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/migrations/001_initial.sql new file mode 100644 index 0000000..cca71c2 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/migrations/001_initial.sql @@ -0,0 +1,31 @@ +-- ©AngelaMos | 2026 +-- 001_initial.sql + +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +CREATE TABLE analyses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + sha256 TEXT NOT NULL UNIQUE, + file_name TEXT NOT NULL, + file_size BIGINT NOT NULL, + format TEXT NOT NULL, + architecture TEXT NOT NULL, + entry_point BIGINT, + threat_score INTEGER, + risk_level TEXT, + slug TEXT NOT NULL UNIQUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE pass_results ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + analysis_id UUID NOT NULL REFERENCES analyses(id) ON DELETE CASCADE, + pass_name TEXT NOT NULL, + result JSONB NOT NULL, + duration_ms INTEGER, + UNIQUE(analysis_id, pass_name) +); + +CREATE INDEX idx_analyses_sha256 ON analyses(sha256); +CREATE INDEX idx_analyses_slug ON analyses(slug); +CREATE INDEX idx_pass_results_analysis_id ON pass_results(analysis_id); diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/config.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/config.rs new file mode 100644 index 0000000..1b3c60d --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/config.rs @@ -0,0 +1,33 @@ +// ©AngelaMos | 2026 +// config.rs + +use clap::Parser; + +const DEFAULT_HOST: &str = "0.0.0.0"; +const DEFAULT_PORT: u16 = 3000; +const DEFAULT_MAX_UPLOAD_BYTES: usize = 52_428_800; +const DEFAULT_CORS_ORIGIN: &str = "*"; + +#[derive(Parser, Debug)] +pub struct AppConfig { + #[arg(long, env = "DATABASE_URL")] + pub database_url: String, + + #[arg(long, env = "HOST", default_value = DEFAULT_HOST)] + pub host: String, + + #[arg(long, env = "PORT", default_value_t = DEFAULT_PORT)] + pub port: u16, + + #[arg(long, env = "MAX_UPLOAD_SIZE", default_value_t = DEFAULT_MAX_UPLOAD_BYTES)] + pub max_upload_size: usize, + + #[arg(long, env = "CORS_ORIGIN", default_value = DEFAULT_CORS_ORIGIN)] + pub cors_origin: String, +} + +impl AppConfig { + pub fn bind_address(&self) -> String { + format!("{}:{}", self.host, self.port) + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/mod.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/mod.rs new file mode 100644 index 0000000..3245184 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/mod.rs @@ -0,0 +1,13 @@ +// ©AngelaMos | 2026 +// mod.rs + +pub mod models; +pub mod queries; + +use sqlx::PgPool; + +pub async fn run_migrations( + pool: &PgPool, +) -> Result<(), sqlx::migrate::MigrateError> { + sqlx::migrate!("./migrations").run(pool).await +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/models.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/models.rs new file mode 100644 index 0000000..5448329 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/models.rs @@ -0,0 +1,50 @@ +// ©AngelaMos | 2026 +// models.rs + +use chrono::{DateTime, Utc}; +use serde::Serialize; +use sqlx::FromRow; +use uuid::Uuid; + +#[derive(FromRow, Serialize)] +pub struct AnalysisRow { + pub id: Uuid, + pub sha256: String, + pub file_name: String, + pub file_size: i64, + pub format: String, + pub architecture: String, + pub entry_point: Option, + pub threat_score: Option, + pub risk_level: Option, + pub slug: String, + pub created_at: DateTime, +} + +#[derive(FromRow)] +pub struct PassResultRow { + pub id: Uuid, + pub analysis_id: Uuid, + pub pass_name: String, + pub result: serde_json::Value, + pub duration_ms: Option, +} + +pub struct NewAnalysis { + pub sha256: String, + pub file_name: String, + pub file_size: i64, + pub format: String, + pub architecture: String, + pub entry_point: Option, + pub threat_score: Option, + pub risk_level: Option, + pub slug: String, +} + +pub struct NewPassResult { + pub analysis_id: Uuid, + pub pass_name: String, + pub result: serde_json::Value, + pub duration_ms: Option, +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/queries.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/queries.rs new file mode 100644 index 0000000..5f24d41 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/db/queries.rs @@ -0,0 +1,92 @@ +// ©AngelaMos | 2026 +// queries.rs + +use sqlx::{PgPool, Postgres, Transaction}; +use uuid::Uuid; + +use super::models::{ + AnalysisRow, NewAnalysis, NewPassResult, + PassResultRow, +}; + +pub async fn find_slug_by_sha256( + pool: &PgPool, + sha256: &str, +) -> Result, sqlx::Error> { + sqlx::query_scalar( + "SELECT slug FROM analyses WHERE sha256 = $1", + ) + .bind(sha256) + .fetch_optional(pool) + .await +} + +pub async fn find_by_slug( + pool: &PgPool, + slug: &str, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, AnalysisRow>( + "SELECT * FROM analyses WHERE slug = $1", + ) + .bind(slug) + .fetch_optional(pool) + .await +} + +pub async fn find_pass_results( + pool: &PgPool, + analysis_id: Uuid, +) -> Result, sqlx::Error> { + sqlx::query_as::<_, PassResultRow>( + "SELECT * FROM pass_results \ + WHERE analysis_id = $1 \ + ORDER BY pass_name", + ) + .bind(analysis_id) + .fetch_all(pool) + .await +} + +pub async fn insert_analysis( + tx: &mut Transaction<'_, Postgres>, + new: &NewAnalysis, +) -> Result { + sqlx::query_as::<_, AnalysisRow>( + "INSERT INTO analyses \ + (sha256, file_name, file_size, format, \ + architecture, entry_point, threat_score, \ + risk_level, slug) \ + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) \ + RETURNING *", + ) + .bind(&new.sha256) + .bind(&new.file_name) + .bind(new.file_size) + .bind(&new.format) + .bind(&new.architecture) + .bind(new.entry_point) + .bind(new.threat_score) + .bind(&new.risk_level) + .bind(&new.slug) + .fetch_one(tx.as_mut()) + .await +} + +pub async fn insert_pass_result( + tx: &mut Transaction<'_, Postgres>, + new: &NewPassResult, +) -> Result<(), sqlx::Error> { + sqlx::query( + "INSERT INTO pass_results \ + (analysis_id, pass_name, result, duration_ms) \ + VALUES ($1, $2, $3, $4)", + ) + .bind(new.analysis_id) + .bind(&new.pass_name) + .bind(&new.result) + .bind(new.duration_ms) + .execute(tx.as_mut()) + .await?; + + Ok(()) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/error.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/error.rs new file mode 100644 index 0000000..f209351 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/error.rs @@ -0,0 +1,100 @@ +// ©AngelaMos | 2026 +// error.rs + +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; +use axum::Json; +use serde::Serialize; + +#[derive(Serialize)] +struct ErrorBody { + error: ErrorDetail, +} + +#[derive(Serialize)] +struct ErrorDetail { + code: &'static str, + message: String, +} + +pub enum ApiError { + NoFile, + FileTooLarge { max_bytes: usize }, + InvalidBinary { reason: String }, + AnalysisFailed { reason: String }, + NotFound { resource: String }, + Internal { reason: String }, +} + +impl From for ApiError { + fn from(e: sqlx::Error) -> Self { + Self::Internal { + reason: e.to_string(), + } + } +} + +impl From for ApiError { + fn from(e: serde_json::Error) -> Self { + Self::Internal { + reason: e.to_string(), + } + } +} + +impl From for ApiError { + fn from(e: tokio::task::JoinError) -> Self { + Self::Internal { + reason: e.to_string(), + } + } +} + +impl IntoResponse for ApiError { + fn into_response(self) -> Response { + let (status, code, message) = match self { + Self::NoFile => ( + StatusCode::BAD_REQUEST, + "NO_FILE", + "No file was provided in the upload" + .to_string(), + ), + Self::FileTooLarge { max_bytes } => ( + StatusCode::BAD_REQUEST, + "FILE_TOO_LARGE", + format!( + "File exceeds maximum allowed size of {} bytes", + max_bytes + ), + ), + Self::InvalidBinary { reason } => ( + StatusCode::BAD_REQUEST, + "INVALID_BINARY", + reason, + ), + Self::AnalysisFailed { reason } => ( + StatusCode::INTERNAL_SERVER_ERROR, + "ANALYSIS_FAILED", + reason, + ), + Self::NotFound { resource } => ( + StatusCode::NOT_FOUND, + "NOT_FOUND", + format!("{resource} not found"), + ), + Self::Internal { reason } => ( + StatusCode::INTERNAL_SERVER_ERROR, + "INTERNAL", + reason, + ), + }; + + ( + status, + Json(ErrorBody { + error: ErrorDetail { code, message }, + }), + ) + .into_response() + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/main.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/main.rs new file mode 100644 index 0000000..4eea8f9 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/main.rs @@ -0,0 +1,95 @@ +// ©AngelaMos | 2026 +// main.rs + +mod config; +mod db; +mod error; +mod middleware; +mod routes; +mod state; + +use std::sync::Arc; + +use anyhow::Context; +use axum::extract::DefaultBodyLimit; +use clap::Parser; +use sqlx::postgres::PgPoolOptions; +use tower::ServiceBuilder; +use tower_http::trace::TraceLayer; +use tracing_subscriber::EnvFilter; + +use config::AppConfig; +use state::AppState; + +const DB_MAX_CONNECTIONS: u32 = 20; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let config = AppConfig::parse(); + + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::try_from_default_env() + .unwrap_or_else(|_| { + EnvFilter::new( + "info,tower_http=debug", + ) + }), + ) + .init(); + + let db = PgPoolOptions::new() + .max_connections(DB_MAX_CONNECTIONS) + .connect(&config.database_url) + .await + .context("failed to connect to database")?; + + db::run_migrations(&db) + .await + .context("failed to run database migrations")?; + + let engine = axumortem_engine::AnalysisEngine::new() + .context( + "failed to initialize analysis engine", + )?; + + let config = Arc::new(config); + + let state = AppState { + db, + engine: Arc::new(engine), + config: Arc::clone(&config), + }; + + let layers = ServiceBuilder::new() + .layer(TraceLayer::new_for_http()) + .layer(middleware::cors::layer(&config)) + .layer(DefaultBodyLimit::max( + config.max_upload_size, + )); + + let app = + routes::api_router().layer(layers).with_state(state); + + let bind_address = config.bind_address(); + let listener = + tokio::net::TcpListener::bind(&bind_address) + .await + .context("failed to bind TCP listener")?; + + tracing::info!("listening on {}", bind_address); + + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await + .context("server error")?; + + Ok(()) +} + +async fn shutdown_signal() { + tokio::signal::ctrl_c() + .await + .expect("failed to install ctrl+c handler"); + tracing::info!("shutdown signal received"); +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/cors.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/cors.rs new file mode 100644 index 0000000..e0dff4f --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/cors.rs @@ -0,0 +1,29 @@ +// ©AngelaMos | 2026 +// cors.rs + +use axum::http::header::{HeaderName, ACCEPT, CONTENT_TYPE}; +use axum::http::Method; +use tower_http::cors::{Any, CorsLayer}; + +use crate::config::AppConfig; + +const ALLOWED_METHODS: [Method; 3] = + [Method::GET, Method::POST, Method::OPTIONS]; + +const ALLOWED_HEADERS: [HeaderName; 2] = + [CONTENT_TYPE, ACCEPT]; + +pub fn layer(config: &AppConfig) -> CorsLayer { + let base = CorsLayer::new() + .allow_methods(ALLOWED_METHODS) + .allow_headers(ALLOWED_HEADERS); + + if config.cors_origin == "*" { + base.allow_origin(Any) + } else { + base.allow_origin([config + .cors_origin + .parse() + .expect("invalid CORS origin header value")]) + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/mod.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/mod.rs new file mode 100644 index 0000000..9c449d6 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/middleware/mod.rs @@ -0,0 +1,4 @@ +// ©AngelaMos | 2026 +// mod.rs + +pub mod cors; diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/analysis.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/analysis.rs new file mode 100644 index 0000000..d524627 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/analysis.rs @@ -0,0 +1,66 @@ +// ©AngelaMos | 2026 +// analysis.rs + +use std::collections::HashMap; + +use axum::extract::{Path, State}; +use axum::Json; +use chrono::{DateTime, Utc}; +use serde::Serialize; +use uuid::Uuid; + +use crate::db::queries; +use crate::error::ApiError; +use crate::state::AppState; + +#[derive(Serialize)] +pub(crate) struct AnalysisResponse { + id: Uuid, + sha256: String, + file_name: String, + file_size: i64, + format: String, + architecture: String, + entry_point: Option, + threat_score: Option, + risk_level: Option, + slug: String, + created_at: DateTime, + passes: HashMap, +} + +pub async fn get_by_slug( + State(state): State, + Path(slug): Path, +) -> Result, ApiError> { + let row = queries::find_by_slug(&state.db, &slug) + .await? + .ok_or_else(|| ApiError::NotFound { + resource: format!("analysis '{slug}'"), + })?; + + let pass_rows = + queries::find_pass_results(&state.db, row.id) + .await?; + + let passes: HashMap = + pass_rows + .into_iter() + .map(|p| (p.pass_name, p.result)) + .collect(); + + Ok(Json(AnalysisResponse { + id: row.id, + sha256: row.sha256, + file_name: row.file_name, + file_size: row.file_size, + format: row.format, + architecture: row.architecture, + entry_point: row.entry_point, + threat_score: row.threat_score, + risk_level: row.risk_level, + slug: row.slug, + created_at: row.created_at, + passes, + })) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/health.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/health.rs new file mode 100644 index 0000000..7d25fc8 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/health.rs @@ -0,0 +1,30 @@ +// ©AngelaMos | 2026 +// health.rs + +use axum::extract::State; +use axum::Json; +use serde::Serialize; + +use crate::state::AppState; + +#[derive(Serialize)] +pub(crate) struct HealthResponse { + status: &'static str, + database: &'static str, +} + +pub async fn check( + State(state): State, +) -> Json { + let db_status = + match sqlx::query("SELECT 1").execute(&state.db).await + { + Ok(_) => "connected", + Err(_) => "disconnected", + }; + + Json(HealthResponse { + status: "ok", + database: db_status, + }) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/mod.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/mod.rs new file mode 100644 index 0000000..2ef0d4d --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/mod.rs @@ -0,0 +1,21 @@ +// ©AngelaMos | 2026 +// mod.rs + +mod analysis; +mod health; +mod upload; + +use axum::routing::{get, post}; +use axum::Router; + +use crate::state::AppState; + +pub fn api_router() -> Router { + Router::new() + .route("/api/health", get(health::check)) + .route("/api/upload", post(upload::handle)) + .route( + "/api/analysis/{slug}", + get(analysis::get_by_slug), + ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/upload.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/upload.rs new file mode 100644 index 0000000..d1f8f63 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/routes/upload.rs @@ -0,0 +1,180 @@ +// ©AngelaMos | 2026 +// upload.rs + +use std::collections::HashMap; +use std::sync::Arc; + +use axum::extract::{Multipart, State}; +use axum::Json; +use serde::Serialize; +use uuid::Uuid; + +use axumortem_engine::context::AnalysisContext; +use axumortem_engine::pass::PassReport; + +use crate::db::models::{NewAnalysis, NewPassResult}; +use crate::db::queries; +use crate::error::ApiError; +use crate::state::AppState; + +const SLUG_LENGTH: usize = 12; + +const PASS_NAME_MAP: &[(&str, &str)] = &[ + ("disasm", "disassembly"), +]; + +#[derive(Serialize)] +pub(crate) struct UploadResponse { + slug: String, + cached: bool, +} + +pub async fn handle( + State(state): State, + mut multipart: Multipart, +) -> Result, ApiError> { + let (file_name, data) = + extract_file(&mut multipart).await?; + + let sha256 = + axumortem_engine::sha256_hex(&data); + + if let Some(slug) = + queries::find_slug_by_sha256( + &state.db, &sha256, + ) + .await? + { + return Ok(Json(UploadResponse { + slug, + cached: true, + })); + } + + let engine = Arc::clone(&state.engine); + let name_clone = file_name.clone(); + + let (ctx, report) = + tokio::task::spawn_blocking(move || { + engine.analyze(&data, &name_clone) + }) + .await?; + + let fmt = ctx.format_result.as_ref(); + let threat = ctx.threat_result.as_ref(); + let slug = sha256[..SLUG_LENGTH].to_string(); + + let new_analysis = NewAnalysis { + sha256, + file_name, + file_size: ctx.file_size as i64, + format: fmt + .map(|f| f.format.to_string()) + .unwrap_or_default(), + architecture: fmt + .map(|f| f.architecture.to_string()) + .unwrap_or_default(), + entry_point: fmt + .map(|f| f.entry_point as i64), + threat_score: threat + .map(|t| t.total_score as i32), + risk_level: threat + .map(|t| t.risk_level.to_string()), + slug: slug.clone(), + }; + + let mut tx = state.db.begin().await?; + + let row = + queries::insert_analysis(&mut tx, &new_analysis) + .await?; + + let pass_results = + build_pass_results(&ctx, &report, row.id)?; + for pr in &pass_results { + queries::insert_pass_result(&mut tx, pr).await?; + } + + tx.commit().await?; + + Ok(Json(UploadResponse { + slug, + cached: false, + })) +} + +async fn extract_file( + multipart: &mut Multipart, +) -> Result<(String, Vec), ApiError> { + while let Some(field) = multipart + .next_field() + .await + .map_err(|e| ApiError::Internal { + reason: e.to_string(), + })? + { + if field.name() == Some("file") { + let name = field + .file_name() + .unwrap_or("unknown") + .to_string(); + let data = field + .bytes() + .await + .map_err(|e| ApiError::Internal { + reason: e.to_string(), + })?; + return Ok((name, data.to_vec())); + } + } + + Err(ApiError::NoFile) +} + +fn api_name(engine_name: &str) -> &str { + for &(from, to) in PASS_NAME_MAP { + if engine_name == from { + return to; + } + } + engine_name +} + +fn build_pass_results( + ctx: &AnalysisContext, + report: &PassReport, + analysis_id: Uuid, +) -> Result, serde_json::Error> { + let durations: HashMap<&str, u64> = report + .outcomes + .iter() + .map(|o| (o.name, o.duration_ms)) + .collect(); + + let mut results = Vec::new(); + + macro_rules! add_pass { + ($field:ident, $name:expr) => { + if let Some(ref r) = ctx.$field { + results.push(NewPassResult { + analysis_id, + pass_name: api_name($name) + .to_string(), + result: serde_json::to_value(r)?, + duration_ms: durations + .get($name) + .map(|&d| d as i32), + }); + } + }; + } + + add_pass!(format_result, "format"); + add_pass!(import_result, "imports"); + add_pass!(string_result, "strings"); + add_pass!(entropy_result, "entropy"); + add_pass!(disassembly_result, "disasm"); + add_pass!(threat_result, "threat"); + + Ok(results) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/state.rs b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/state.rs new file mode 100644 index 0000000..936f3f7 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/backend/crates/axumortem/src/state.rs @@ -0,0 +1,16 @@ +// ©AngelaMos | 2026 +// state.rs + +use std::sync::Arc; + +use axumortem_engine::AnalysisEngine; +use sqlx::PgPool; + +use crate::config::AppConfig; + +#[derive(Clone)] +pub struct AppState { + pub db: PgPool, + pub engine: Arc, + pub config: Arc, +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/cloudflared.compose.yml b/PROJECTS/intermediate/binary-analysis-tool/cloudflared.compose.yml new file mode 100644 index 0000000..ef876d9 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/cloudflared.compose.yml @@ -0,0 +1,27 @@ +# ============================================================================= +# AngelaMos | 2026 +# cloudflared.compose.yml +# ============================================================================= +# Cloudflare Tunnel for production remote access +# Usage: docker compose -f compose.yml -f cloudflared.compose.yml up -d +# ============================================================================= + +services: + cloudflared: + image: cloudflare/cloudflared:latest + container_name: ${APP_NAME:-axumortem}-tunnel + command: tunnel run --token ${CLOUDFLARE_TUNNEL_TOKEN} + networks: + - app + depends_on: + nginx: + condition: service_started + deploy: + resources: + limits: + cpus: '0.5' + memory: 128M + reservations: + cpus: '0.1' + memory: 32M + restart: unless-stopped diff --git a/PROJECTS/intermediate/binary-analysis-tool/compose.yml b/PROJECTS/intermediate/binary-analysis-tool/compose.yml new file mode 100644 index 0000000..7928b85 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/compose.yml @@ -0,0 +1,92 @@ +# ©AngelaMos | 2026 +# compose.yml +# Production compose — Nginx serves frontend + proxies /api/* to backend +# For Cloudflare tunnel: docker compose -f compose.yml -f cloudflared.compose.yml up + +name: ${APP_NAME:-axumortem} + +services: + nginx: + build: + context: . + dockerfile: infra/docker/vite.prod + args: + - VITE_API_URL=${VITE_API_URL:-/api} + - VITE_APP_TITLE=${VITE_APP_TITLE:-axumortem} + container_name: ${APP_NAME:-axumortem}-nginx + ports: + - "${NGINX_HOST_PORT:-22784}:80" + depends_on: + backend: + condition: service_started + networks: + - app + deploy: + resources: + limits: + cpus: '1.0' + memory: 256M + reservations: + cpus: '0.25' + memory: 64M + restart: unless-stopped + + backend: + build: + context: . + dockerfile: infra/docker/rust.prod + container_name: ${APP_NAME:-axumortem}-backend + environment: + - DATABASE_URL=postgres://axumortem:${POSTGRES_PASSWORD:-axumortem}@postgres:5432/axumortem + - RUST_LOG=${RUST_LOG:-info} + - HOST=0.0.0.0 + - PORT=3000 + - MAX_UPLOAD_SIZE=${MAX_UPLOAD_SIZE:-52428800} + - CORS_ORIGIN=${CORS_ORIGIN:-*} + depends_on: + postgres: + condition: service_healthy + networks: + - app + deploy: + resources: + limits: + cpus: '2.0' + memory: 1G + reservations: + cpus: '0.5' + memory: 256M + restart: unless-stopped + + postgres: + image: postgres:18-alpine + container_name: ${APP_NAME:-axumortem}-postgres + environment: + - POSTGRES_USER=axumortem + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-axumortem} + - POSTGRES_DB=axumortem + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U axumortem"] + interval: 10s + timeout: 3s + retries: 5 + networks: + - app + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + reservations: + cpus: '0.25' + memory: 128M + restart: unless-stopped + +networks: + app: + driver: bridge + +volumes: + pgdata: diff --git a/PROJECTS/intermediate/binary-analysis-tool/dev.compose.yml b/PROJECTS/intermediate/binary-analysis-tool/dev.compose.yml new file mode 100644 index 0000000..05eecbd --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/dev.compose.yml @@ -0,0 +1,95 @@ +# ©AngelaMos | 2026 +# dev.compose.yml +# Development compose — Nginx + Vite + Rust backend + PostgreSQL + +name: ${APP_NAME:-axumortem}-dev + +services: + nginx: + image: nginx:1.29-alpine + container_name: ${APP_NAME:-axumortem}-nginx-dev + ports: + - "${NGINX_HOST_PORT:-58495}:80" + volumes: + - ./infra/nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./infra/nginx/dev.nginx:/etc/nginx/conf.d/default.conf:ro + depends_on: + frontend: + condition: service_started + backend: + condition: service_started + networks: + - app + restart: unless-stopped + + frontend: + build: + context: ./frontend + dockerfile: ../infra/docker/vite.dev + container_name: ${APP_NAME:-axumortem}-frontend-dev + ports: + - "${FRONTEND_HOST_PORT:-15723}:5173" + volumes: + - ./frontend:/app + - frontend_modules:/app/node_modules + environment: + - VITE_API_URL=${VITE_API_URL:-/api} + - VITE_APP_TITLE=${VITE_APP_TITLE:-axumortem} + networks: + - app + restart: unless-stopped + + backend: + build: + context: ./backend + dockerfile: ../infra/docker/rust.dev + container_name: ${APP_NAME:-axumortem}-backend-dev + ports: + - "${BACKEND_HOST_PORT:-36968}:3000" + volumes: + - ./backend:/app + - cargo_registry:/usr/local/cargo/registry + - cargo_target:/app/target + environment: + - DATABASE_URL=postgres://axumortem:axumortem@postgres:5432/axumortem + - RUST_LOG=${RUST_LOG:-info,tower_http=debug} + - HOST=0.0.0.0 + - PORT=3000 + - MAX_UPLOAD_SIZE=${MAX_UPLOAD_SIZE:-52428800} + - CORS_ORIGIN=* + depends_on: + postgres: + condition: service_healthy + networks: + - app + restart: unless-stopped + + postgres: + image: postgres:18-alpine + container_name: ${APP_NAME:-axumortem}-postgres-dev + ports: + - "${POSTGRES_HOST_PORT:-5432}:5432" + environment: + - POSTGRES_USER=axumortem + - POSTGRES_PASSWORD=axumortem + - POSTGRES_DB=axumortem + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U axumortem"] + interval: 5s + timeout: 3s + retries: 5 + networks: + - app + restart: unless-stopped + +networks: + app: + driver: bridge + +volumes: + frontend_modules: + cargo_registry: + cargo_target: + pgdata: diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/.dockerignore b/PROJECTS/intermediate/binary-analysis-tool/frontend/.dockerignore new file mode 100644 index 0000000..a0256ec --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/.dockerignore @@ -0,0 +1,15 @@ +node_modules +build +dist +.git +.gitignore +*.md +.env* +.vscode +.idea +*.log +npm-debug.log* +pnpm-debug.log* +.DS_Store +coverage +.nyc_output diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/.gitignore b/PROJECTS/intermediate/binary-analysis-tool/frontend/.gitignore new file mode 100644 index 0000000..61cb0c2 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +.vite + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/.stylelintignore b/PROJECTS/intermediate/binary-analysis-tool/frontend/.stylelintignore new file mode 100644 index 0000000..37da03e --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/.stylelintignore @@ -0,0 +1,22 @@ +# ©AngelaMos | 2025 +# .stylelintignore + +# Dependencies +node_modules/ + +# Production builds +dist/ +build/ +out/ + +# JS/TS files +**/*.js +**/*.jsx +**/*.ts +**/*.tsx + +# Generated files +*.min.css + +# Error system styles - ignore from linting +src/core/app/_toastStyles.scss diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/biome.json b/PROJECTS/intermediate/binary-analysis-tool/frontend/biome.json new file mode 100644 index 0000000..7f29029 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/biome.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.json"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 82, + "lineEnding": "lf" + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "jsxQuoteStyle": "double", + "semicolons": "asNeeded", + "trailingCommas": "es5", + "arrowParentheses": "always" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noExcessiveCognitiveComplexity": { + "level": "error", + "options": { "maxAllowedComplexity": 25 } + }, + "noForEach": "off", + "useLiteralKeys": "off" + }, + "correctness": { + "noUnusedVariables": "error", + "noUnusedImports": "error", + "useExhaustiveDependencies": "warn", + "useHookAtTopLevel": "error", + "noUndeclaredVariables": "error" + }, + "style": { + "useImportType": "error", + "useConst": "error", + "useTemplate": "error", + "useSelfClosingElements": "error", + "useFragmentSyntax": "error", + "noNonNullAssertion": "error", + "useConsistentArrayType": { + "level": "error", + "options": { "syntax": "shorthand" } + }, + "useNamingConvention": "off" + }, + "suspicious": { + "noExplicitAny": "error", + "noDebugger": "error", + "noConsole": "warn", + "noArrayIndexKey": "warn", + "noAssignInExpressions": "error", + "noDoubleEquals": "error", + "noRedeclare": "error", + "noVar": "error" + }, + "security": { + "noDangerouslySetInnerHtml": "error" + }, + "a11y": { + "useAltText": "error", + "useAnchorContent": "error", + "useKeyWithClickEvents": "error", + "noStaticElementInteractions": "error", + "useButtonType": "error", + "useValidAnchor": "error" + } + } + }, + "overrides": [ + { + "includes": ["src/main.tsx"], + "linter": { + "rules": { + "style": { + "noNonNullAssertion": "off" + } + } + } + } + ] +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/index.html b/PROJECTS/intermediate/binary-analysis-tool/frontend/index.html new file mode 100644 index 0000000..299f87a --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + Binary Analysis Tool + + + + +
+ + + diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/package.json b/PROJECTS/intermediate/binary-analysis-tool/frontend/package.json new file mode 100644 index 0000000..005327e --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/package.json @@ -0,0 +1,51 @@ +{ + "name": "binary-analysis-tool", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "typecheck": "tsc -b", + "lint:scss": "stylelint '**/*.scss'", + "lint:scss:fix": "stylelint '**/*.scss' --fix" + }, + "dependencies": { + "@dagrejs/dagre": "^3.0.0", + "@tanstack/react-query": "^5.90.12", + "axios": "^1.13.0", + "react": "^19.2.1", + "react-dom": "^19.2.0", + "react-error-boundary": "^6.0.0", + "react-icon": "^1.0.0", + "react-icons": "^5.5.0", + "react-router-dom": "^7.1.1", + "sonner": "^2.0.7", + "zod": "^4.1.13", + "zustand": "^5.0.9" + }, + "devDependencies": { + "@biomejs/biome": "^2.3.8", + "@tanstack/react-query-devtools": "^5.91.1", + "@types/node": "^24.10.2", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "sass": "^1.95.0", + "stylelint": "^16.26.1", + "stylelint-config-prettier-scss": "^1.0.0", + "stylelint-config-standard-scss": "^16.0.0", + "typescript": "~5.9.3", + "vite": "npm:rolldown-vite@7.2.5", + "vite-tsconfig-paths": "^5.1.0" + }, + "pnpm": { + "overrides": { + "vite": "npm:rolldown-vite@7.2.5" + } + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/pnpm-lock.yaml b/PROJECTS/intermediate/binary-analysis-tool/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..b785d9f --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/pnpm-lock.yaml @@ -0,0 +1,2636 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + vite: npm:rolldown-vite@7.2.5 + +importers: + + .: + dependencies: + '@dagrejs/dagre': + specifier: ^3.0.0 + version: 3.0.0 + '@tanstack/react-query': + specifier: ^5.90.12 + version: 5.90.12(react@19.2.1) + axios: + specifier: ^1.13.0 + version: 1.13.2 + react: + specifier: ^19.2.1 + version: 19.2.1 + react-dom: + specifier: ^19.2.0 + version: 19.2.1(react@19.2.1) + react-error-boundary: + specifier: ^6.0.0 + version: 6.0.0(react@19.2.1) + react-icon: + specifier: ^1.0.0 + version: 1.0.0(babel-runtime@5.8.38)(react@19.2.1) + react-icons: + specifier: ^5.5.0 + version: 5.5.0(react@19.2.1) + react-router-dom: + specifier: ^7.1.1 + version: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + zod: + specifier: ^4.1.13 + version: 4.1.13 + zustand: + specifier: ^5.0.9 + version: 5.0.9(@types/react@19.2.7)(react@19.2.1) + devDependencies: + '@biomejs/biome': + specifier: ^2.3.8 + version: 2.3.8 + '@tanstack/react-query-devtools': + specifier: ^5.91.1 + version: 5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1) + '@types/node': + specifier: ^24.10.2 + version: 24.10.2 + '@types/react': + specifier: ^19.2.7 + version: 19.2.7 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.7) + '@vitejs/plugin-react': + specifier: ^5.1.1 + version: 5.1.2(rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0)) + sass: + specifier: ^1.95.0 + version: 1.95.0 + stylelint: + specifier: ^16.26.1 + version: 16.26.1(typescript@5.9.3) + stylelint-config-prettier-scss: + specifier: ^1.0.0 + version: 1.0.0(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-standard-scss: + specifier: ^16.0.0 + version: 16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: npm:rolldown-vite@7.2.5 + version: rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0) + vite-tsconfig-paths: + specifier: ^5.1.0 + version: 5.1.4(rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0))(typescript@5.9.3) + +packages: + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.3.8': + resolution: {integrity: sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.3.8': + resolution: {integrity: sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.3.8': + resolution: {integrity: sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.3.8': + resolution: {integrity: sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-arm64@2.3.8': + resolution: {integrity: sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-linux-x64-musl@2.3.8': + resolution: {integrity: sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@biomejs/cli-linux-x64@2.3.8': + resolution: {integrity: sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@biomejs/cli-win32-arm64@2.3.8': + resolution: {integrity: sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.3.8': + resolution: {integrity: sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@cacheable/memory@2.0.6': + resolution: {integrity: sha512-7e8SScMocHxcAb8YhtkbMhGG+EKLRIficb1F5sjvhSYsWTZGxvg4KIDp8kgxnV2PUJ3ddPe6J9QESjKvBWRDkg==} + + '@cacheable/utils@2.3.2': + resolution: {integrity: sha512-8kGE2P+HjfY8FglaOiW+y8qxcaQAfAhVML+i66XJR3YX5FtyDqn6Txctr3K2FrbxLKixRRYYBWMbuGciOhYNDg==} + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.20': + resolution: {integrity: sha512-8BHsjXfSciZxjmHQOuVdW2b8WLUPts9a+mfL13/PzEviufUEW2xnvQuOlKs9dRBHgRqJ53SF/DUoK9+MZk72oQ==} + engines: {node: '>=18'} + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@csstools/media-query-list-parser@4.0.3': + resolution: {integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@dagrejs/dagre@3.0.0': + resolution: {integrity: sha512-ZzhnTy1rfuoew9Ez3EIw4L2znPGnYYhfn8vc9c4oB8iw6QAsszbiU0vRhlxWPFnmmNSFAkrYeF1PhM5m4lAN0Q==} + + '@dagrejs/graphlib@4.0.1': + resolution: {integrity: sha512-IvcV6FduIIAmLwnH+yun+QtV36SC7mERqa86aClNqmMN09WhmPPYU8ckHrZBozErf+UvHPWOTJYaGYiIcs0DgA==} + + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} + + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@keyv/bigmap@1.3.0': + resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.5.4 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@napi-rs/wasm-runtime@1.1.0': + resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oxc-project/runtime@0.97.0': + resolution: {integrity: sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.97.0': + resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@rolldown/binding-android-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + resolution: {integrity: sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + resolution: {integrity: sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + resolution: {integrity: sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + resolution: {integrity: sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.50': + resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} + + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + + '@tanstack/query-core@5.90.12': + resolution: {integrity: sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==} + + '@tanstack/query-devtools@5.91.1': + resolution: {integrity: sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==} + + '@tanstack/react-query-devtools@5.91.1': + resolution: {integrity: sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ==} + peerDependencies: + '@tanstack/react-query': ^5.90.10 + react: ^18 || ^19 + + '@tanstack/react-query@5.90.12': + resolution: {integrity: sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==} + peerDependencies: + react: ^18 || ^19 + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + + '@vitejs/plugin-react@5.1.2': + resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + babel-runtime@5.8.38: + resolution: {integrity: sha512-KpgoA8VE/pMmNCrnEeeXqFG24TIH11Z3ZaimIhJWsin8EbfZy3WzFKUTIan10ZIDgRVvi9EkLbruJElJC9dRlg==} + + balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + + baseline-browser-mapping@2.9.5: + resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} + hasBin: true + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cacheable@2.3.0: + resolution: {integrity: sha512-HHiAvOBmlcR2f3SQ7kdlYD8+AUJG+wlFZ/Ze8tl1Vzvz0MdOh8IYA/EFU4ve8t1/sZ0j4MGi7ST5MoTwHessQA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001759: + resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + core-js@1.2.7: + resolution: {integrity: sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==} + deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + css-functions-list@3.2.3: + resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} + engines: {node: '>=12 || >=16'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@11.1.1: + resolution: {integrity: sha512-TPVFSDE7q91Dlk1xpFLvFllf8r0HyOMOlnWy7Z2HBku5H3KhIeOGInexrIeg2D64DosVB/JXkrrk6N/7Wriq4A==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + flat-cache@6.1.19: + resolution: {integrity: sha512-l/K33newPTZMTGAnnzaiqSl6NnH7Namh8jBNjrgjprWxGmZUuxx/sJNIRaijOh3n7q7ESbhNZC+pvVZMFdeU4A==} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hashery@1.3.0: + resolution: {integrity: sha512-fWltioiy5zsSAs9ouEnvhsVJeAXRybGCNNv0lvzpzNOSDbULXRy7ivFWwCCv4I5Am6kSo75hmbsCduOoc2/K4w==} + engines: {node: '>=20'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hookified@1.14.0: + resolution: {integrity: sha512-pi1ynXIMFx/uIIwpWJ/5CEtOHLGtnUB0WhGeeYT+fKcQ+WCQbm3/rrkAXnpfph++PgepNqPdTC2WTj8A6k6zoQ==} + + html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@5.5.5: + resolution: {integrity: sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + mdn-data@2.25.0: + resolution: {integrity: sha512-T2LPsjgUE/tgMmRXREVmwsux89DwWfNjiynOeXuLd2mX6jphGQ2YE3Ukz7LQ2VOFKiVZU/Ee1GqzHiipZCjymw==} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + + postcss-resolve-nested-selector@0.1.6: + resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + qified@0.5.3: + resolution: {integrity: sha512-kXuQdQTB6oN3KhI6V4acnBSZx8D2I4xzZvn9+wFLLFCoBNQY/sFnCW6c43OL7pOQ2HvGV4lnWIXNmgfp7cTWhQ==} + engines: {node: '>=20'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + peerDependencies: + react: ^19.2.1 + + react-error-boundary@6.0.0: + resolution: {integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==} + peerDependencies: + react: '>=16.13.1' + + react-icon@1.0.0: + resolution: {integrity: sha512-VzSlpBHnLanVw79mOxyq98hWDi6DlxK9qPiZ1bAK6bLurMBCaxO/jjyYUrRx9+JGLc/NbnwOmyE/W5Qglbb2QA==} + peerDependencies: + babel-runtime: ^5.3.3 + react: '>=0.12.0' + + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} + peerDependencies: + react: '*' + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react-router-dom@7.10.1: + resolution: {integrity: sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.10.1: + resolution: {integrity: sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rolldown-vite@7.2.5: + resolution: {integrity: sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + esbuild: ^0.25.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + rolldown@1.0.0-beta.50: + resolution: {integrity: sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sass@1.95.0: + resolution: {integrity: sha512-9QMjhLq+UkOg/4bb8Lt8A+hJZvY3t+9xeZMKSBtBEgxrXA3ed5Ts4NDreUkYgJP1BTmrscQE/xYhf7iShow6lw==} + engines: {node: '>=14.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + stylelint-config-prettier-scss@1.0.0: + resolution: {integrity: sha512-Gr2qLiyvJGKeDk0E/+awNTrZB/UtNVPLqCDOr07na/sLekZwm26Br6yYIeBYz3ulsEcQgs5j+2IIMXCC+wsaQA==} + engines: {node: 14.* || 16.* || >= 18} + hasBin: true + peerDependencies: + stylelint: '>=15.0.0' + + stylelint-config-recommended-scss@16.0.2: + resolution: {integrity: sha512-aUTHhPPWCvFyWaxtckJlCPaXTDFsp4pKO8evXNCsW9OwsaUWyMd6jvcUhSmfGWPrTddvzNqK4rS/UuSLcbVGdQ==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.24.0 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-recommended@17.0.0: + resolution: {integrity: sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.23.0 + + stylelint-config-standard-scss@16.0.0: + resolution: {integrity: sha512-/FHECLUu+med/e6OaPFpprG86ShC4SYT7Tzb2PTVdDjJsehhFBOioSlWqYFqJxmGPIwO3AMBxNo+kY3dxrbczA==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.23.1 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-standard@39.0.1: + resolution: {integrity: sha512-b7Fja59EYHRNOTa3aXiuWnhUWXFU2Nfg6h61bLfAb5GS5fX3LMUD0U5t4S8N/4tpHQg3Acs2UVPR9jy2l1g/3A==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.23.0 + + stylelint-scss@6.13.0: + resolution: {integrity: sha512-kZPwFUJkfup2gP1enlrS2h9U5+T5wFoqzJ1n/56AlpwSj28kmFe7ww/QFydvPsg5gLjWchAwWWBLtterynZrOw==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.8.2 + + stylelint@16.26.1: + resolution: {integrity: sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==} + engines: {node: '>=18.12.0'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + update-browserslist-db@1.2.2: + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + zod@4.1.13: + resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + + zustand@5.0.9: + resolution: {integrity: sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/runtime@7.28.4': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@biomejs/biome@2.3.8': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.3.8 + '@biomejs/cli-darwin-x64': 2.3.8 + '@biomejs/cli-linux-arm64': 2.3.8 + '@biomejs/cli-linux-arm64-musl': 2.3.8 + '@biomejs/cli-linux-x64': 2.3.8 + '@biomejs/cli-linux-x64-musl': 2.3.8 + '@biomejs/cli-win32-arm64': 2.3.8 + '@biomejs/cli-win32-x64': 2.3.8 + + '@biomejs/cli-darwin-arm64@2.3.8': + optional: true + + '@biomejs/cli-darwin-x64@2.3.8': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.3.8': + optional: true + + '@biomejs/cli-linux-arm64@2.3.8': + optional: true + + '@biomejs/cli-linux-x64-musl@2.3.8': + optional: true + + '@biomejs/cli-linux-x64@2.3.8': + optional: true + + '@biomejs/cli-win32-arm64@2.3.8': + optional: true + + '@biomejs/cli-win32-x64@2.3.8': + optional: true + + '@cacheable/memory@2.0.6': + dependencies: + '@cacheable/utils': 2.3.2 + '@keyv/bigmap': 1.3.0(keyv@5.5.5) + hookified: 1.14.0 + keyv: 5.5.5 + + '@cacheable/utils@2.3.2': + dependencies: + hashery: 1.3.0 + keyv: 5.5.5 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.20': {} + + '@csstools/css-tokenizer@3.0.4': {} + + '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)': + dependencies: + postcss-selector-parser: 7.1.1 + + '@dagrejs/dagre@3.0.0': + dependencies: + '@dagrejs/graphlib': 4.0.1 + + '@dagrejs/graphlib@4.0.1': {} + + '@dual-bundle/import-meta-resolve@4.2.1': {} + + '@emnapi/core@1.7.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@keyv/bigmap@1.3.0(keyv@5.5.5)': + dependencies: + hashery: 1.3.0 + hookified: 1.14.0 + keyv: 5.5.5 + + '@keyv/serialize@1.1.1': {} + + '@napi-rs/wasm-runtime@1.1.0': + dependencies: + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@oxc-project/runtime@0.97.0': {} + + '@oxc-project/types@0.97.0': {} + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@rolldown/binding-android-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + dependencies: + '@napi-rs/wasm-runtime': 1.1.0 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.50': {} + + '@rolldown/pluginutils@1.0.0-beta.53': {} + + '@tanstack/query-core@5.90.12': {} + + '@tanstack/query-devtools@5.91.1': {} + + '@tanstack/react-query-devtools@5.91.1(@tanstack/react-query@5.90.12(react@19.2.1))(react@19.2.1)': + dependencies: + '@tanstack/query-devtools': 5.91.1 + '@tanstack/react-query': 5.90.12(react@19.2.1) + react: 19.2.1 + + '@tanstack/react-query@5.90.12(react@19.2.1)': + dependencies: + '@tanstack/query-core': 5.90.12 + react: 19.2.1 + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/node@24.10.2': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@5.1.2(rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.53 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0) + transitivePeerDependencies: + - supports-color + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + astral-regex@2.0.0: {} + + asynckit@0.4.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-runtime@5.8.38: + dependencies: + core-js: 1.2.7 + + balanced-match@2.0.0: {} + + baseline-browser-mapping@2.9.5: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.5 + caniuse-lite: 1.0.30001759 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.2(browserslist@4.28.1) + + cacheable@2.3.0: + dependencies: + '@cacheable/memory': 2.0.6 + '@cacheable/utils': 2.3.2 + hookified: 1.14.0 + keyv: 5.5.5 + qified: 0.5.3 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001759: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colord@2.9.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + core-js@1.2.7: {} + + cosmiconfig@9.0.0(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + css-functions-list@3.2.3: {} + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + detect-libc@1.0.3: + optional: true + + detect-libc@2.1.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.267: {} + + emoji-regex@8.0.0: {} + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escalade@3.2.0: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-uri@3.1.0: {} + + fastest-levenshtein@1.0.16: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@11.1.1: + dependencies: + flat-cache: 6.1.19 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + flat-cache@6.1.19: + dependencies: + cacheable: 2.3.0 + flatted: 3.3.3 + hookified: 1.14.0 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globjoin@0.1.4: {} + + globrex@0.1.2: {} + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hashery@1.3.0: + dependencies: + hookified: 1.14.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hookified@1.14.0: {} + + html-tags@3.3.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + immutable@5.1.4: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + ini@1.3.8: {} + + is-arrayish@0.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-plain-object@5.0.0: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + keyv@5.5.5: + dependencies: + '@keyv/serialize': 1.1.1 + + kind-of@6.0.3: {} + + known-css-properties@0.37.0: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lines-and-columns@1.2.4: {} + + lodash.truncate@4.4.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + math-intrinsics@1.1.0: {} + + mathml-tag-names@2.1.3: {} + + mdn-data@2.12.2: {} + + mdn-data@2.25.0: {} + + meow@13.2.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-addon-api@7.1.1: + optional: true + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss-media-query-parser@0.2.3: {} + + postcss-resolve-nested-selector@0.1.6: {} + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proxy-from-env@1.1.0: {} + + qified@0.5.3: + dependencies: + hookified: 1.14.0 + + queue-microtask@1.2.3: {} + + react-dom@19.2.1(react@19.2.1): + dependencies: + react: 19.2.1 + scheduler: 0.27.0 + + react-error-boundary@6.0.0(react@19.2.1): + dependencies: + '@babel/runtime': 7.28.4 + react: 19.2.1 + + react-icon@1.0.0(babel-runtime@5.8.38)(react@19.2.1): + dependencies: + babel-runtime: 5.8.38 + react: 19.2.1 + + react-icons@5.5.0(react@19.2.1): + dependencies: + react: 19.2.1 + + react-refresh@0.18.0: {} + + react-router-dom@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-router: 7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + + react-router@7.10.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + cookie: 1.1.1 + react: 19.2.1 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.1(react@19.2.1) + + react@19.2.1: {} + + readdirp@4.1.2: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + reusify@1.1.0: {} + + rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0): + dependencies: + '@oxc-project/runtime': 0.97.0 + fdir: 6.5.0(picomatch@4.0.3) + lightningcss: 1.30.2 + picomatch: 4.0.3 + postcss: 8.5.6 + rolldown: 1.0.0-beta.50 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.2 + fsevents: 2.3.3 + sass: 1.95.0 + + rolldown@1.0.0-beta.50: + dependencies: + '@oxc-project/types': 0.97.0 + '@rolldown/pluginutils': 1.0.0-beta.50 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-x64': 1.0.0-beta.50 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.50 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.50 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.50 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.50 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.50 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sass@1.95.0: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.4 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + set-cookie-parser@2.7.2: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + sonner@2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + source-map-js@1.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + stylelint-config-prettier-scss@1.0.0(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + stylelint: 16.26.1(typescript@5.9.3) + + stylelint-config-recommended-scss@16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + postcss-scss: 4.0.9(postcss@8.5.6) + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.9.3)) + stylelint-scss: 6.13.0(stylelint@16.26.1(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.6 + + stylelint-config-recommended@17.0.0(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + stylelint: 16.26.1(typescript@5.9.3) + + stylelint-config-standard-scss@16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended-scss: 16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-standard: 39.0.1(stylelint@16.26.1(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.6 + + stylelint-config-standard@39.0.1(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.9.3)) + + stylelint-scss@6.13.0(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + css-tree: 3.1.0 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mdn-data: 2.25.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + stylelint: 16.26.1(typescript@5.9.3) + + stylelint@16.26.1(typescript@5.9.3): + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-syntax-patches-for-csstree': 1.0.20 + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) + '@dual-bundle/import-meta-resolve': 4.2.1 + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.9.3) + css-functions-list: 3.2.3 + css-tree: 3.1.0 + debug: 4.4.3 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 11.1.1 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.3.1 + ignore: 7.0.5 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mathml-tag-names: 2.1.3 + meow: 13.2.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-resolve-nested-selector: 0.1.6 + postcss-safe-parser: 7.0.1(postcss@8.5.6) + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + supports-hyperlinks: 3.2.0 + svg-tags: 1.0.0 + table: 6.9.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + - typescript + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + svg-tags@1.0.0: {} + + table@6.9.0: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tslib@2.8.1: + optional: true + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + update-browserslist-db@1.2.2(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + vite-tsconfig-paths@5.1.4(rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0))(typescript@5.9.3): + dependencies: + debug: 4.4.3 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.9.3) + optionalDependencies: + vite: rolldown-vite@7.2.5(@types/node@24.10.2)(sass@1.95.0) + transitivePeerDependencies: + - supports-color + - typescript + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + yallist@3.1.1: {} + + zod@4.1.13: {} + + zustand@5.0.9(@types/react@19.2.7)(react@19.2.1): + optionalDependencies: + '@types/react': 19.2.7 + react: 19.2.1 diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/android-chrome-192x192.png b/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..7788e65c52e54991feed172efbefde92f682e2d3 GIT binary patch literal 41495 zcmXuKXE+@1`#!vS)J;N=s1ZRz^j_9#5d<4iqZ84Ivid3?L9`%xUoCp?T}1D_m&DrD zBbLQ3%is5Tj^B%!c{6j&HP?O5RnGIgA|YC;)D%xB0002B+8bq^yKB$?j(encXPq@C zG63K{KuuXe*N0#XPoDO1S#LACYEvp9UC-214me+#(l+lv{YLoS{d{kXm2C0?)niu$&{?mSC8(;G+1FtRSL)%SSO()U0;{%@x&5GzZyogaz=Zs6%y@6Qm4rZ``Z7aC#L z(!VW0hWi%7an|G9!74#*F3{b)m6*_x10oGGBYz@@p`nf;GGykHTh=jrKRBNt|M+T) zjPA)Pbs5!|m4omh2tSj&~V-;4om#l+mqp1tpKz zH%CHxsWyFG<~jvvIQQ$pys?}X7I0CffW7x0niJl=>Wjd4Ov3bCQj$NwcyO46qU zf=`IlU)7{H`ILBtT8LCTQ#n3@;PZ4~1Af?LrzXbSPX+=4+n59fO?~ce@&j>1@aKF< z-@{Dl+81W9P_NU+i0#dd4Kpc%Ga^nhfJUdUXFP@P(@hJR{Na_bI^CrR(u0I%@e=1f znQ$R1=wlrb!nxlG`vA(+bD?|sc)r(oE`ZHL2gg7i@I1u_ZcBS2`V;U;g!=ez)w&K`A6Rfq0mB=i&(X+$&a-^Hv6h4rFVp zUP@hDTv$tI(`RxMLe9X`1Sj|#`z~bFkt>EoC02wSKIUqHeC!3FdI07Fse$;Cig%N|sr)uHN1f z{AL}DW@_8>kBP-tdGP-|7t_!E1z;8=cs@y>^m44Zx!J&>=9|7!hIrr^1Cjy|!R;~) zwgwh~U)<;6H8U9?H3>IJ>Np{mXz_F@0nvo-g(Nq@M|!qqRst~cRO4hk)_Hs!66_b4 zSxkm1Ul+`E`lsYqkl3KaCe7#I^UsXVf=ftJLf0z{4I*gtax=nvFjzr7Ho2mV7b z^M(*fg$Oq#7(2==_mdg^JHU#T7`GCZ5Q~c!I#XHjv5u^MeGT6xp`)}IX6V0LZeT?p zIC;l~l28xG=c)aLU<6DUt;Tvfj$=ZVJsWNlfN&-GwRpl<-;@NRw}wb}URwz1gQr)1 z#OV4c0-~>A&=sa&6&mNN!g28*J&JSMdGGE_zO5!^-q0`K(K)Kl0wCB+hUX(So=cC= zh#8#Ty+Fb@yH`P`=I^=X@Ml3_O$nLdw(0Rj+ZyvAPJB4nT}Q=byi04g;Zih{!uRwZ zvH-y6;2JnEO7vYv)p#5zlLyB=v5@7nfqrhN&?fo}|M5vCYNhJ@HIjt=MevA>IM}Er zLtfW7QKRR%moMSiOc7zYlt8QsH=OI+Et$(_Y~#;Q=45XJB-e>XBpZERK)A9K$Bknp zKh8F%2M^FYRK$?^9Try+=v$xBj&JyQl*p9P9})84?K^ZYKTIbMnFN=Se2~%1a)3^emnmLSE!jv5%PV8;^v$rvX=V|XcH>d0(Y#;yG4 zpCr72Og1lTVH<9$72h?kZ6@mbcAm1N5nNQsQi7Iz&6?s@(&5`cULrkcZb!Q^U?**_ zRIdaZtgfKuK`bCsKSO*Cb@0Tx*#V(=f+#!w$E9`w1)9YHjAzTl_`6L@z!M#1ZTSkP z>;iH8PyIdS3kdmi>7JpsVBv@CH{#*KKHxkD{xJRRqHCIZC1*;>G32iqb1l=Jfj~lD z=WiaqpDpHT%H1i$_2g=1%tlxBoej)OQUg2y-$~#juV{ES|FUNBDJhTDRX$BPyq?`c zZBY=6c*L|G7Bt4-k^SmZ5iingV^RjZ!o(!GbewVO5`TD%PX!DBC83K++DT2}79Ole z)Y_unrxD|=nRuPKbBw+2jZUye^PU31$o@wcV2mhgH7&Q?eQUl0{AUn!APec8lR1Hp zsHDr3)OjV8eV=Vh3Tx9eY$kZ_weEem$d!xDG_RK^3moG$rvSI+nqP=$hgz4fH-*VE zR~Q93r-q{6f3=j{UGeNuK#<19RuRz*?AJfNe)GtQH46jRY@Ls-W%WpNiXr+7{#D1e zKPUuFzs6ANW$i`X?TQeBD1;-x4ncv$6wCpu}$;kLvx_Zq! z_3IE49il!MDCR%Dc?p$1cfDRJ-g_c9_Jh@d8cnEg1I+{^`I|fUSoMV5Z7JX4zd!Av zZ>+mwef1z7fG861aQk257)KeHlZG83J=sbmVIwQpBSc782XgGjmk|@&w~sWJC&0Nh zDK#eZ`En^F$G#`iOKD#C;o5;_-0$o>C4-{RRZ8A3ESlW;rc*l4g?3)s_*dF~w44q2`E+ycxAA51yId{0%sl-cxn0I7ixgEhTiK5*i^vt?Rg9iRxBH|+GcpO}@AA`RubrF=& z@VqQ(xR+W>q&}gf9h~wEUxv7zH=1?Lrx`K?HYWTCEus>I4sQ~}hd#&e6TOMjwsa?> zdrUJUnyPL5F003ymB-5)JY4T$;3CLc#s;Y(-p_!&%}!bwn5yC<8QInYQQ^cgu>(^J zA1&TR-)IV8zBog2-tN0za3`wSo3BnxOl;XsYq|z{sHx0qFf}MSugZ?3QhJD+pAKux z|3YQ0Fr6`s=m?=QE2{GLoSwO_`Vcy04DA_%4#bo9zg$btMgK@;sA5*^)Xio*9m&Vq zdFVm7=P0oSfL(nT19+7WN+{Q^_Mii_GBnQ`ykR5LO`QBHjSD0R-q znR>)J27_LSakP`h#B%@;?ieEBaFtjactsjyZ;7kAJO0jfB_ZEc3F0eym=&Kh zH{!-X?wPA%!~kJF6|4a>A!xy2Quoz`J!qpn_WVmICj-i^I!;hjn3?+ zep7J8G_ykQ%7?Nlh1R^i;F**YEwiJlcnAjUrYA64axI;qNdaYEguzjv>VsZaRuzqJ zLUb~(mt4n)tWWBEJt&CojC(R*H(|Y=MmB@2;Z@_#6=Y?0`()DtHU1A?;Q33VHJJU3if^+=0L_ zJ88k0^=c+-Z0BVuYnl9xI zA*^S{tcyInEciK#>f8id0M87W!`Pi=;HWw+e3l79 zvcwD8dosgN#wvISICECVhW}Vq4wh7CThU_7EuHqE{;1ArLZ#t=p#Q(nFDMZ-GwDzZ2ejaM#n8si^cge&4~e`Gcfhq zikqm}T`tpl=X?k^E2Qcp%2nz~>>7L_-YNae`4FH-Vtt!`Dd?T*M##t1NDVo65Pbi3 zh+Ar#2*+mCL4|24KwB~Yph^l|I0IXc0`_O=(0Bic4g9SzcB#gBM#sVP`&Ck)~0Zu7zfhe}yB{G8d?3a)cdYE&o^+B{24 zrocj%7~pHian9h!ziPF*kRcAux%*?!=65EEJd${W$0Q%jk!Dsc7v=8(i*x8Scp=0V z)~eA$-e4(12wwKL*FuE2F98%_%mQv0Cvdp7q)WLu0KHr6A`!b;a!aN}o zQcTopT&UR<2*&^1Own~D*7YBgAR9BV9Q?*&-0|@AQ94B4o7b)8$*R-A**_4!9Ol7i zUn%24A!{Faa564U1fd^7h$e|c(V0fotcuu3i?tsegTFqE zSvv2-&sEnMAqP{cau9m)gWz!?p%sgIpl+w{>%WxrJg{bci#+)#DarSX9T9n&A87Tx zW%S|4Yi4*|(c9;hD{W-z33_GzaKY!cTy;dOvfTfL|43a#k^hbah-u-G0RE%yjRnc- zrrM6kW)KF~B#@e+CCBGDWlMEbIqBT5*GyxGvJ*cYBC-i^o{gM2Tj6KOw92T;Ca%PLS)P2R|f5zW1(nk7{YSdWmYXrD1yMjBzxLJAoK&7-V2TX@6{puCTAX9 zD7p3ce^I{+1ybrli>gnocL7PE&o`ZD(h}MBUv6=tUB?@`WeNl!e^uq~z0!J;+N$8p zASv4Gc7LH@7KviJBCs*x=ZlN5s_kDpsRPEWzJHBL@mT!1Iz!+kw$6wC4=(b72H(WWuHlhkIczbA1KBEaS(|!06JSS7x2xgF$E8#c+oAu z=EyPBBfYEBh{3p2kX?QFdCU~9U=!KnlUMCY1qJWf&Prq>$|jmtukEEVIF|6(dU^+? zSNHUhWyRmuLV?4-tN`!zK;ry&>ATpOiv5rn;=UX?L~Xb`-xv~B7Wb?vrMneeY24YG z5t!{@KsNdzScSzca{MwJ<*=>=xhkRIG3Xeg=4rL=YFxrRnV&SJAW*0AsJiN{s~(6< zbB&GI7cp?AtO2^qjq4_S#5`o{TTC1h@Qx-&sE@vc45W&1PaiZ+ zDkcv(>&fSORFf2v>}6^1h#fc@9t=oq=C^nrfg_c)6LAeCIv&{zy_PiCCLR6op-YHl zgwrEgESJnUjn;5(?hse;D_|eqWYf{ukoFq$xS(GwyY6U(%wwrI&Gl6T=zmU!HvyBs zL2&sSc1%w1p#rU=P^~C zHLmHHD()Yp#O!Yu5%L&N`sPuWX_>Jw9|AwagK(?io-rvz4raiwZ7Az8M$K-m*azqD z%hpXGjs&_4J>M6rk&8>)uN%_`d1I~(1q5Pf(N>3xTNcYdx)e9>ki%M<{gyPV$uNp({W~yQyM=GM1Jz5rFPaOCKp2J~&y8R4-gz(>}yY znTP0mvf4VSzIiOJNv*Jh27mToWns^dyZakz^K8T_%b+z6Yf3{hlIC;3{(%O&pbc#j z3vUbmv1s04n=j6_GOrO^%kP`FRuWLZC(uCd`?T6T)IGGessluH&&GM&p!H;?R@0XX zHaOM9Poz?$$@6B=cTgHjihq*P-Q-RZJl`kn;1ui^E`b%KND8G)-fzy)SZC}p}kptGiJr_Z7@bV2;+MT3}}~9`2AA zD|(7I?v@*g*6-3X*}oS?(sW;Br;LGxk)8-V4^8$jIsPRqVp6uZ*J66R^dWxv1gkD&DCs! zve_7--fa+A^|*!y@v2F;35@CE87^Pndp%U6l(%h}#zg@QQXk zNy>fgNHp>)l(uNO$eQuloY>1-2J+LW6j}%K!-&RMXB-bDR&(l8#hee^q88q0L3%O=4A+W6kGtvD0U%8hK43=f?wt5H1eB zQZH~_&7-H@^obu4#mi5^=9wW#jD-Os*-9uOCS<_%acA-ACTR;U^L=|4+DdM+jVg21 z_@bS8`TR>)%@}2pP@VWuFh4Iq--Y`SI2Xy-O3=@GT8Vj}?~!f}C>n3|e+tg~3lA)( z;nU(oST!hMT>VM`>MrnW+5!6f$`c(hnt`H3DsKiOE+Ixb@10n=H{iQ%pcRbb44eO0 z^U@XlP9mH-204mcJtBb6WN-)bLPC8pC(4l;PB@D7h0-=41Ij@#qER7xXn&rCq}nBi zM+g44fu1|8jA$fTE8F=^SSx`V0iW-qaaFUW6u}6_)xnM~YMg>ITME{4SYosLo&$^3 zdwzJ#bQ^VwpM@nU{Y}j_1C0l2iq^tK9?S<3C7N0y2`ifgzU&q>`%mcgB?NISowdy> z`D=UL-jTNerzto{JMD@#U1Uj_RjU}6E}}j-85#eCzrg~pX10ReHS%+(n&0_a8t;E` z4@FC7IKOqK+!uNWOpef7S$TXFcS;mZ{wXKCc9Iqu^3aO^@t*XYIZaHx7YsS*ImYHk z*uW0=qgIDQ|nGfov#sI*OVu3$QaSUV|6O0gK9k{imo%~cXP(41!cGOkWh%D#cX#;PYy z34-!Y z&C4l8ezo!GtnE9RW~iEo1|>h{Eq5;1+Al$obVGzRTOXcts%;{m)I7D<{gr0WMF-dN zT=fjd8$mrtjdJyZWv{)8@PqauI2&VY&9c#4%r;C{2iyZ~&<&}AIKyTZt~gjG5JJy= zWi9_q0%Vj9lYWG_7N*CLV~(GY_qejj)-h z4MJp^7l5rP!-|~VzMJqqsB$2ybX!kq&h!=mdYjv? zE$h3M<$73mx#F-tLNlJl=>|H;LF_lR(PD>MA+-?9z)Ov=4(I=&K|%p7^6D;buZrse zLf!pkxT?*h!`rG33MNN$%H~o;Y%wUW&s{6(Dhzh6XWAs4LBT#U^+`(^v)K~ z_WR$b#yx0llq|LLvUr#2o6&Wbkui+oX42<+^f~NE`Zrwei4@wT_Mz@dZLjn{AB`vm zvg{8UHnTlvT0Cr8_t{(eu?GMDuy~n!;8Js6r&P8B$Q!;@uf`Zz09{{zqnk$e(`{3= zuz=9I&lnfT0>5d5pC!`_sSWHEK?;7l%4`LGEI5XOu~Jkv*_V0@m4jcNiu5^2kGj5& zq(h6)?K)}+WolYyfXt-kqMss)=0O_%9zge2c%bDgMiM_)`M%_+w7Fg<@A$41G5MDnFwa6t)QAPV!YId6 z95BSD>Py;xdMrKmK+5vmIw(k>_CUVyEB|PH-&1Zw>ExfRXmFe_@>k8UcD9|+H|=t5 zmUyV)Dx{=3w0}vpJ5=yBmC+8Lb@89=qpN_yZhxWkhoxV`or?b9IB88knrNLf{&+Fb zd)=zx9CZp(qIpAKeH3Y7(%u1}=T2-LK*VeEfpFrx+yall>5FL67< znI9*Lewr5PD~yiX_6J@#353g%hZn+9yGntW$hWjEO|RsrRzj@8ZC3ZAg}ot1rW@bE z$oHoE#3KT%NAEMQSNPeK&J)wNw*_d|BC{FY!zTGvj?=RfrFnU2yGLoWk{Y23`y3bi zG#n`79QHkfL8~g9;61*peMJ%@l_uwxB~2JNLc*6Bxgf%nla)C&5(w^LYWrip1$-ur z!-IO}F+3h`^1@*HI>z;m1b|vQ)k&D18YdS}DUL}PqD(K|)0=AXuOstti4@btTV20P z{|^h`YtmWLaw_V`rnQ#Y_V%@AH z2VRlY?8@-@!WI)yXGd0b$;BR8OD(}9skxzWk6yJ(-+KN<~v%rmIec?U#cNk>h}q~Uag zWTiAioi!1=;?Exyx9TDw$Q1H6sx2qt%3}TE;Rh{8%QnpXFQeZ#{HuBz)Vm)h>0p*%x2x1_P*(27;oMj z`GW##hZvZ{z7Si2Gji}-YgzPD*FSAegA_*|pWZY-9ZJlUot7KMT-+9}Xt&MT{>N|X z&fJjD4C`Iu_hpDu)A=Kb*<)(8o9lRxBB;C`3@D4gn*Z=#=(dCmZDf-YJ;paA#OSWO zgZfTnI90o0Xk$_9!%OL`XqKd7LlZh-wO*xFaga=)_M%F#fW@gvVLr~pVCmP&2RC&B z@ttKx`HSrF^8$^)@4N23-oF2EUNs&%XRF53NaZE{D-4?e^WOtUrT5PTYOnhanWR62k_Ojmi#yp!0t^1Jf?HG6vKgf4V!{q2H*88(9 z_+bH1H&8yeG-e4OhMPV%JfaFUZU`(w_Yr-1g^kXkmTc%$4fXQei~snT;7ct(=U!QT8Nf zzlaF>us%+7kG=m;jRy65D+&4|KOj86oLlvs$pFn*vu8t9ktZ=(c}SbrsTH5HFN?LD z;PF`7;ve^D*E2zF_KN;hqRIWGn)HIaj0lteP}QeaLo4CBJ8JP{UNgWy$bihOYvr}Z zMke!{vBVPzbI+Hct8ctpulf7*wFtG7p5mXu(1}L*S;S`49X645HXE{#x+Vb@CDUJq z0g;_(&QKH=j7^IBnJcbdn=^cAKY!}TQ#vKF<*gGot5U{vo0AP) zm^M&*7^H9x{(Y`8+BKP|g)YasxDE zg57SB(0Ib-&?I3h_tc6(Z>gqI!z;$-AJV$S9PLbG;)!3Oq0$6N2Z$VuKNDRcHFd;;@GrR zaGcfW;kq6j*LD=u(sb;umlfXpP$_qo57Fxkn#L>IXG}8KprISeb`>>R?~3N}hMPNm zBqQV`l_7dT&#e!eWeaoHqK?OMelUoKe~*qltf_`XVchn6>@J0(?0n_xw5P zTG@-;Qm>o>Pij9oGEgG3?AsJQ-co8Bd%e1-WuzS%vlkeduENIY^727GvbRZE8EX?K zMFK~|^SGj)HC4&fr9a~^(5!KyyvMCJ>RegVF!;duWF$m{>)3zBgD0YR5~f|*sO_%8 zI#y}zpSM4DbKhH@^`wf7e<18BP?1%a;Z}CRw_28ygI&bdY_nv{eFhs|GOBEP-revB z>e!*Hd*pts%{eEd#_lnp33j&T*EnOE^;C09IleqqUKZmV2LX;X20K1O4TzHr1W95pCJ{7Jbg3@pChi&PE1<=US*?Hx+N8s+Tw;lgtwX zBxCxA={(&C6b^JmKr-EL*?`C8tTG@on*6$yk7agQ1QA<$^Pmt$KM8|=88Lx_dg8oO8iWP%Q7gP0S%H!)g zhaKu%my0`kvwvHy`2{?g>^|D}nSNgt$FTm?N=;ZaKHX#*_bw%~ER(xI00hKi?d;5_8=-K)AV+7B1~VwG9qm-tEF;hX<`} zs5j`H%Wc!3NH3GN>6VuazaF$hb9Gjig4Xkf!EWze$XwQZgl?p~!95QVo;7ny$V!_- zkL7>?8SK+jWeL~W;qEW40Zl`XP7K*3O#C@+{^blJfUNt7PKIWLQx4#&en{pw{}11{ zr?d*&%)BPT!>Du%STA|y%!!T*gj;}wkcZY_o$-znfxN|yp22c=K4x<$;|0{t-BZ3U zeZ#l#yZYvMj1FIFG@H{JAopP;3|7KflM){oto`r%*JcB|jO(L?9{dUmKrP2w4tO-J^N9P;7Ws-lNtf)!d)3iHGpF z-z&c^4PRv@`dYYl;>skwLt3t;@jf5DAGS-K+Fs3Oy*Ax8eK$txI2?@qQk~_$n(!Kc zrIL>IszgaKXbG;sTUcB_(}uTn;B99y0{Ayj z6dKo;xpDiZ1Cuj*!VS!dZf65A0YRS9-`M2o<8lJ|>V8dMD5r)R`wJ;AzlOa;uh0xZ zqM9(YIuqLOzInxuSl+`Q)^qtj?fzEFDlE=fRwMt5d3^#Fx20w;?3d=h#H?{gDu&GJ%YVvy} ze$@|g;RHAt7li@C1Wcb!V$fEt%r^b44Kf zfLd2RJSRP7eWC4-fQR1p&;D+5v!)?#SrBz2_yJ&0uG>VPgT$G zaanPlf!u9@52v9CN_1v%>e zi3*!1y(%BJ&k;&s?`@VcHLGjSTEhCtZ=TSu!WgvM_cDC0*TPyZ&cY-UX?Fv*IE;l> zL8$(9#P>$pbS@K571o8hofr-1HYx-#!g=0&Wqo;Z)?P))ewf>I$D2e1;T0uIpGg2+ zzN15Yw>VTH=O=0J@mSNCw#>Ud8QBy3sE2AzGpLad+>F>7KW>%h#y_fUhidjkGFi33 zG!Hek25uIHTb9fpGe|vPXfGTt-^?_5#L$*l%f!8P7zXlixhsxt4aK4O$gk~xTVCB< zc3uov*YlrSJxt&RME+F#Iy6)FA2U{Wo15dFK38cXKv z%)k<9!x`}f2y|K3leChkOo?);s{T%;;S50l`UySmS?e0sxEG-Bq#fYBEH`iremigF zkGtK+4yk+04PTrwrm3!PhsZOC7FE#+)z=(aD1)r#^#JFNBNn!p!Sl$|t3(TTn~qBn z^CyGaCZ;r>lo?n)Zm4-CR_^*ub02A}FW51G1Aa0DD0J*;x<+|-AXrY$o4e(1F5_-n zAV_&^qC9z?mfdZEJg(sK_!i$is2b3xnq-1{VBv2%oo?>aQv{xZPD5emwhv)}PZxJM z=O#YNgjcvWuYObg0n3V{JwaFF!Y`LMFV`=_ZcoB5`!{i+m&uzjjqCm6&gW*Q;mre% zfw#^l2i>y53BL`X8N1P?u#Fn2Aj7;Zb&@?l||LV?N?jL*c$&qXNdus<1T@} zGK+)O{-!2tY*s-}5>y7Jj1qVA>zlfC*w-x7LhA0fFrN7aw4F73VY?3Uk6+GFvS-4# z!$wqHz>EcxjNp^l=_Yr=5;AwaQ4EKGc9ofy$y0&txc@+e4y-{{2X{)kKQHItX1kj; zF7Fo>uAPjtA@bpEzl5SciF_JbEs3(H!j|0+;)qY1>aVqw_}g}(9I*RyaOy`)Ei zWNI_9_ZnvH@?aBt8^sVfQ~a90&^LZOz4&~8FFoy56KSVCqGpH6uOEh9cj_HFi6lk_u-szbe%Xi(M-@j9R!xsQa zAVAp;<47b+)w-{{cj?hg)}+HUGBXNKiv;iN4qnxV5?)75M*W1iYoS zcI0kbchNKQkp><6yjw54L9e za+Qwm><|jVnB!4C``O+qk$I!i#L1)y-e0DR*&eGakD(8-^X4~2mz|sVTx`)Y=jKCA z#E1#cUr8uz?W!le@0=oSlLA!G5?sRjZ%;=grQOX!(nv!M|a%wX;Wq$?Kvn?Oxnf zoj2h=i=A z=B?x%J?&p^CES@NO)$OkUzq{@%HU_>0kru&`O(#Aj52sHO$ETq9nyA3=V{lGf$oI+ z(U0ba^JaVq=2f$UxfXwt<`;}gw-qKZKGexSz5_EBuNP6x`P$ugYkq-`>qE?bUC)b4 zV~#Tj`Xw>=h`TD1N^28jhIwTEpx8y~QQ+*8)@4OgXKSI$QvLu5>7h;h`X-C3|LUsf zD*zqq%U8Vtwuda8WQWPpci=UaOcJDl39p~aJfSlLVLz5A1} z4b{PD5H>N?^N_t!NfvLf9j$%UpdGz%?5-T~=6kUMtK}+Pl}kbD_W zFMH8CNj<}AdBe}K2f5$+AmQkf07W)R?d-32r^pTd7TYPbF2j@H5J!LbRRc-ml&{yA$OqMzs+p!})@@!usWM6!0#Z7VyJ}>{uEVUczwCYAw*Y&h= zG!F0ZP`4en(7h*+N|@@|c#-P)!5cuBvfF0!1m4p84bb1r@YfyY<_>-XE#^P7Z%jxAuUJpP?!9Nk?BkOI(^s5t*xIy`AvRigm|Ls3Z`_I1Zyjq}^QOauj`>_MFelhTi zA)>>Sd24GICfV6j+55EgCfJqT?2#6GG>2U zL=`*V`!&UTrDbhGMz@TGWM4o$>Yq@Dzn4SE- zK)@eL1a>kgaLJWmJ_VI!vaVRo_dzpG7X9Wr^%iZu^^=T^546v8wo1$%z#h0fYNqe* zT@YzeA!IFGXyw{wJ|N~jjXaw04%qP?NX$7aWUPA&8+>zterKy{D;0fl!9Os0bEe(T zWPXd2%&8CJMpTWd`R|N#BMw_VPC^E4PYkQjSe+MM9s@g4_pj0eGSZ%h_vofKQx5&! z&gz2)zOg?oo{Q%y4SsUKtO1R=s=D4$@HVWWcCCxWRL1Kw&M6MM{cpxg#w{t2Mt$@c z>^s?>U9R`5)_PEJ!z&LJW|QUTQK>Y^AI@%&pCvKPy@@QmP+l62Jl^g;G3*;(G<$qJ z#6fNI5lK&yM-!q}p2an|w=uNWq_y}KBGdEZE&j6UzYca3fC zKJN1Itd66^#x!ICyJkB&mw{H&%KtI1^MAtrK8S>5=Djr;>}%TkN=uA< zD#z#C|FpkjHB#+wn=FUc4QSKru!hY*^T$aNlU9=6VW{tHkXK5guzAo1W^AXA0oCb! z_Bbn|p@N*2gRqcN%xa$!GW_C>0aCLB3@^W;s&6q^stVDk;!eINz%=}=#Ona4x*V&&EBO-sA zcrxLAZMlIv96UL)H)&@%`OkKfH3+xizeQyG%oX;dwpo5#gWM_wA-26enHOzen58oI z{l`mHnXM8(V``leD7a{U;cEA?ETSk59TEChN1-Q*Qg=CDnfG;Bg@`U2Rx>#Coc@5= zt%e<#WB&W_Z%Q+94srW%=45%WC)JR`J#;|`nR^rKi=y27|-IhMqYUriYJQ!M8iY~s*< zdvk?gyZIHw7$<*oS!7a=uj=XQ-g;DhubWfk9!*nXbn}l24<7T5PV}hxV{ep_e=2v7 z(8qfTw~sRcQEh+g;)Xl$m9CL&H+gJd`MVjG)2ZBSK-U|-qw@+g7i<2~thvsEffHT^ z*PK#y3A};Z`|^G9z=`^?T>JckcK4ae>T5xl_mdP$71ayK4J;{f{1H+iJu_UEl1Z1~ zd+d||FsOpl@M2Cao6b*~EjTstC+(j;&aJ(>K7Jy*oAE-{ou7YO?x??`FsM%zBw%*= zBKP5xN^1Nk!!!}qw@rYraaS#I(Q&tzag)iPGrTA&S|yq5LM!-y3)R;n)n95pnZ`1m z?hgRA&R1RIj&W7T6jvodp@Da!rME@hXQ}A%$<)4DYndYcAzVJk9dYj{WUD3rWb>l> zM*d`%=%QcW?0NQean(}Ps>+N5Smnq92ne z_rUS8G(yN_ffpDY7Fe^LT=`y-Em)XFXfwE1p@;%J%q;*=o1RbC?~|?Y1;3SLsQ;G- zAD;309Q+;3!smQg-=S!Dnm#g!NlC&MRW@xHW@)y;&;NsS z3mo{n33Ip%Y4jXvx4pR8&Gz2$zvbV&zK@?sKG~YY9k%?%WvXv1d@2xHnZwSKuxY_W z3T1`ah6&wpXM300w|nyaau;jwwJzxeNa#;SGyz-npo97?q(G4G`()|W+G&HE%M%1r zxfQcu91Z)E6$2t^^_qNGN@1R>bt;B(48o*tU2OitE#BWu?#@8o05KW6{-r9`2)NjNDhmQMTTU*`W) zs#@_yr^tIh6xzeJ;dVlDw!N7@te56gVwVkuL!j@ubo-M ztU20fdWSgpyVxx1k7i)edc}ZWVqdiR^;W?6Wy99a_Iq!}4>Zsv$8oA&&0DqW{Aj(P|Ca8m(KGCqIpnK%+sW+zRS7^p zsLK8vm;PdTH(L~AU%&dTdHmbXQXm}=%IEP?w>hOk9STemDSy7PP1~xVnc|cV&~Z!a zZu$inHNiDB9Tj4~;Dks?yg7jT@e$9^prkoqy?xcW>0F}4z3w5YCKcATua0rF5@sG8 z8<@O^tq}X|5qdYb+{-!xn2qd<&Gyxl|B2koUb8uNAXALDs;`r~bq2~?9UZUV&5>;d zUAf*mUq(Ypu#47~rLIjovO7{pZ^fzrOS@Z>N?MsU7D~bNMzm{M*ZJDcb!k`7fY5UDdQ%e{V>h;z1f`076;Llmu`M!D z(qgz8?9SB;7{4p14M+YrgbXF*R54GRrdcX+SFfM?cPU&TEZlAU& zr~a3EuBOB0*IXzRYwZhnY5k*oIgZ5JUToFM#xvMHXqLy=pA?y&Bcu_5>KkWy z=DdO{IMms2S)LoDa)`&1Jnq{IP}4slM7o)8nypS%-9h)S3Z$Twl>@w#Hi%%f`qJi^ zEPhXWGSDl70;4~ct940B0Sm(r^xL+UC)x%DWlRdxU)IaxXD=5wujH0px1y|JT`Wv@ z+)(vb$13Ru8yq`%fW@d8kL+*sH`DN0@POqM4+|oZ@SXK8AtRUfm zv7UlIq_V-!NO-!yEeb{GULk_0xQ@0318^;X2L!QyhvIkK{oWTPluRl0nh6p$!|(K_ zjXWU}#~E!BfsMo9C=KZySYg4*=aEu{pE)Laj2V%c%)I+Ra_wV&3;4>R0}&R(!o zR@(z-V=Hl1k7SmUG#~LpuvbxI zuztJo-jNTQ1mCyG?9QhxYAs%a5NsEzBX0k?^1RP`tXFd*3v*&zlqmAGgmC~a*oV=;xtv`7P5sGK8UhY_JR4^o=$dr z2CVI#WZi?|j3XFrn!oTA>&f1;B9-npPMo}Nrq9;KG@T^ z_Ak6{ucd7ic_LfPl1hHbb$X?rNNz|HpY+Ns{zcDVpNdzFyo*TW8E@E88CSG|4w1#B zfAC`qn+;sbyOL!8{JN}qFYsclc|}wmh8T=rr@qMS5AHpT z@Fx=CZrd@64#l#P3oElH@!feYJ?@4g(2l2ybC;zN8@j!bP6*o>WsWn(Z||b8oV3}& z(@dKnrBNOcZ}pv2Q0KBKQgg@g87Q2Fvpy|Za5Oup#5rCz7Brq-d8Ot)JmkI9Djp&?UXCa1G;<$Km>mqUOJ@yqO)yO6_@((p z8xlXA0*l)Woc^xxDt|W+cQ%@vwLLs2f7cgj%O{}Hnv`$TCoyp?OQ2Hx{G+Fmc3kxK zvVVrJ@GIoB$#WS@j^C-Q)LaQ#T+)M@$QGr-RMu~l>{4Wl0tta*?-?%W8W*oeN;mpn z&Gr#nMr>Z^Sg-PTaDyqv6l6Dw?Vyixeg2&wRnefT3|C~fXJ-~Z)tc}HUhSkM)NXi9 zG88;psxw(j5HsCf^B-7iGs-3;J-I7skuzRraNV~2x&+9BRuXVf(Ss2hVLwy3_pE^K zlQVrg64-RKVJUtA(rAJNrn<}E;J+6ZUd%^}h5+gsQepl$6x-bmjnT1UEANr<9Z#$n zR7IRjAsiO_JTEmhJUVVSfk@d2x!ZMiiRd~%z!Bcx>F;GQz(vhENq6`7h{xr|88H2P zAsl$Ape&c9{*!*Cc0`&vNHsK3R0U#dT}T>b24=^0!8oT&rV68Wg%Pw}kynmr#TATd=is^kyx`^~IBX;Ugo)&ba~4(d+BBo5H9 zKDxEyw|!Q#>e^(QNMY+`!dWs(T#T>}#KDl5ETT zUfB3OodHWCcG>Ud+~oYRo0JM;+s=-QM$ME4%&*B$X8BfP=eJRS7I8mx*;9=C$P>Z- zm`7$VPDlCk)^(Gg5(JgNg82{j$8isZRO|Q)h&ekej)Mh|173W7#R(yLqm}JF=$c2n zzavb%M^T*BzG&|k!z}&(p~IGf;|3S+L)NXj zD8xAdy9&CRzNPKBuwUkFDRrxQH^F0)F+DY^%w+#NVPn2deiw40xk`su8JP|YVDm0h zDN%WE!L`auxd+zon72g>It-OoLukBfk_Q+IKBs-dUKjNuJpDUHda+bqFe|W2`KRe7W^G~J zXO7B+3Rxc=U@uQtdE3&l+C3udNCfs+bO;eBA^fh<6;xaWyMCx!xP0qSLm+{Y13Dc7 zS)P~<)Vm46O;_ow=UJR>>27{LyiSa>r3BFS^*i~kXwX)aiojs|f{)ZCMS)&dB(W(0 z+Cs@av8jxT^3#+$wA@-9meqZ77kSDBH#V+XkHu0Zl(RDX149H|aH2YvQW3jKn!wqZO z|icjPBG{>mC z$ske!NNIHbg29XBXb!|3aKN9%9@>2cdmS*p`924XHOBQ0~sRkV{J?wd9r%vulmeavqeuQ4B%^fXWuS#Kegy4YdA#_V{bD6(y zVe&jI!@US2;8`h9=|)$2Wyq;=zwWiLZYFGEqM7Pa_e6)^{ONC>fA@LfRuMFk+G1^i>TQS$ z7Ui9YUhW=UQIDB%ZuU%Z_E8ZVM8I##Rh0fdNWIy(^&GV~e`tz2`T?$8EaqyPJIWrQOmE-I zNIi&x8)I;eXayHPLSpcR9{th@Aa=oe8V1?VJ+7b~U{WYVxn#FsCxh3Ffj;xZY#!9U z4$8Q6H7s2pEv~K7WkH&np1$=G(YV#ekKO-%JcT-)B50*D1*pDhcXM`_Eq?!Ki0i`@ z4#=Lm|1J$b=SCi4BIKq{_KYnS+fpaDzZ1@_3__0IX7)-qd3pG|r~P5F-lY_n%~(m@ z(PTGHJy@vMM%n(gccCOLUjl8u|5*p&W3bDE!}Rq-l@Gr{+lwcktiC%@cCL-S5Opr^!XxV-)z>}=^Lp17=NnPS#ozJ9#pAHy4 zE6VglXx;Vf^NkxLcT!fGO0_d8KAb*FhmJgVY1lUlC8Rabf&qj=>7@hz9Z z0U_fA2+)T(qgftMwRtS{D$U9twQ)9xYppd!Hm5#?Wz$_ulv*@h9K}oRG;go&S<7Uh zE;WsWmt`g5pJEByZtW3?7vToiJYKW~v570w=$itjw&!+{-Y0#7Cw+OCvf77H9y=Wb zZN#W{GW5Y&c^?0vgN7<`}N&uJ4Jir|}HAPuDA?{-1;M_^GYh z-zOpO`8Dke?!UQUdhR`sZBujOxhNy>vRX-yK%vF$P-Ro~=z~h9YY)wPr`EfSO>-_p zZm%b{4EUK%)V;HdQX++QO-w0~EKeQqnFX^+sgx17!Hp;q?(U-hJPo=_l8)wSSvO)J z(_Ol#QR-yCT`Q8g4?H+CbWtZ81SaV5Zzo^zJDkN($s(rmDfG@JTFPa>f!>4J^uWW( zUtVc>S*XH12ee>^KXg2)=oDc5>mtGQTIGHJmtOcupFY*DnZ#iQN<4Y!FM*2DTmI;l zSP|;l0+}b~hAl13NlEo}&1641l6qs-)t8^2Ntb}=cj{h)z$rrXlAK{q<05iEOaxKQ z0bwd;G)|qBRX>kN3PIkd8s70r9K46lj?HL(*m&?@=}hBIMe!M#4Ic)dz=L;L2@?*2 zKp@o1zG$7SdPhlz1*L-B-&b-`Th5K#YoU!-f~bSH^|IENM*9_)&13wCnV=zO$^+N7rndiB43M=1SQq7~6011J3Cp zmQB#aHEa$eXW$n7y>o}Ut@k=e40ilRPg$c8S9;_hf}R_SNWU^;G)9k9G8+g6YT=jc z0D2mH$froqh)I4v(WZ2S>2E$=N;AtgL$T0#rglHugM^GLt^w}pqunU3BK;M;awjm? zpwBQHzT9soiX!b*#q%lks6cH~3I9$S=@!o=Ja=v6jaXRFvV`Hq@##HX%)Tx#0zlVH zT9*Bw;VP)_kHLLq`5FMXZUL&lZ<1kRjW`gEYxuOCcCBz|Lx{~1r`-7AGyOt^s#-+4 z#kCYaqFiAz6G3V5zQByq3o#FrPLr^n&`fMbIYt?CfDr$#^l{EpS~Glu6aF<3Y`L={ ztJ;+&w+fDFh>K?Gxi=NsdG5Tamc{BDcHu@4W!WY;tN#SmH0!O|5O| zW;b;3*zfRR&Fb08HCvb*N=@pdHfwY$P`)DA9LblNCzInoMG7LApYq+mV^o>ok}$yFfK9y5v)!ZG>% z6hr8d&}_kYTry8A6JG~c-B_)zF#3rC;7!y+EOln(`W>bZZ~gdGyVN&kI{o{!J0+|S zdakash!QDx_xwO=ToYCS_VE?>uVK@k3fDG5YQcHB(^^oS<$KQdSTJ0m5k1%l_Sj!| z6QGbQjYhn6^#5v3QqYm(k5{4?IBb)r^xdvvo%*HZQA*4F`&>`Y{V5|3&-~*BC#h8$ zuC(T~BEZVmFvQlvI0b^6hbvreJ z!(es{4LDu%Xw=y)P|gfPWm48JEGY|XrYB5UnJcYArNMv1O^nZN|B-?fJ|_K;$n^ z4XR6n&@HzfJ3%{DFwDf?{!qpv!+HHvOj$kTBq@$Et`9ACQR z+3SwN;8RKPCg%gKa@&ST?Vg(WBQxPOSY}t9U;*Lx&f$L>qvo{*K&nZugCL%i*GpNQ z_6J;R?Dl#Sey#3B>JgbLV$Ou67J736UP0P+dr5HcR1Sx+iorB}B$N|GsREf(zZWXa z{Qh9ia0R`}dF!R$f0N@@>$GwTjxp(k&>>G(%#stA;o2Z?g)gp%?jFDszOVV~5hw$F zgfEL#^~BKwHyGK~k9%D-x>PtYoKti3w(HG2LYWZ7&{=!e+}Tj=ck{(~ZH0f41-wG@WYb6FL zwD5%*n!qy>f=3Hl^iv_$h(oxrShs8bj;U2g#m>meQMv1sZxs%`@H4k;C54^gcdWz@a)tOlYU| zOQBqDD-jY)a}8uDNLS1=ni6b@&FMnr#x~H@MrW8Hb89H@Di><7(;$7+Z zGJX(t{M`SI_33{s+xqPVsv6O@XwjNQ;&48@Ogk|nB)yq{ZI~3MuiY zZ`2X|MYbETmN691Cj&Nu)8Ud`kK28IHifnDMXi^`0T5V)Wr_{DCdWT zGV1R@Z^bAzm#<7;w5?94e-$K~roIu*WxpkXnWQRU#nd4B?(AbGTqIN% z8D5r|(@7AvnA&cCYVve(8NYq}D>$y>i^beYrvPQE&Ez!MHJslCeKUjiasS1(?`300 z>tEwi0-_hOTl;oz(FRL_bm_|TN9dz=X&ON~mHBn-o5E&fp6*X!$Msg|HyEI5CRIiK z&2H&S{ADiMc~)Ejlzs|&`M^b;uDcBRUgbHMV-wPYh^KA*%yP9x;~cF8>*n$M{#Vl4 zc93nS@TTJw?|DJc>1@;DblK^>?0grP<#FQYh^VKlTf`?2g^jOKU1&WI5dGt4FC1rL zNwotKij&Z_D8zF`4(6On+botidV`K@?u?Pr@xz1~p}HRe17kRCFH>L@~bHP=l5+{CRvV`Oo_- z)#83W(h$Jb@&|tCb7{tR_#Bj2XK}sjn$8nm&Dh2*1@HD+QwfV4OdkDntU>R?p1+KC zY%=SqgKodWH(2ix!n>;`VO42Q7f+8Md>m?CV%dk^G8TKfaq6U;TEZyRNn7|LlXsx4f7d|5gA! zrZcgf)pO2N;e1kP&>M()?(}$~f;ON52j>ceGW?QvbQ382Xa+ARCiSrwM$^ z^)AknoxDftSC+k0N#7lb{e!{yE-TO)ks9EBV&~sKfj~HcRI)e3Pa}Xd>I(&m@Cnab z>gew=j1nC^H*TMcz#vWwKlMhgdG~05X3{MiV^c9Fti6u?7N$|mxO)lvZGgv`+ZxY3 zL^ZQ_>zec3F;1RV*!U^`R=TUip8e^*$_!pf+7CWUSXNp3Q`(EiE%jOP?D}94ZnpF1 z=6ue&*;_HIGK(qgslfd?4mi7IQr>5XNF+bwS_+QT$ zg%+ip+A*h?@#4@ujVDek+wod{qEEsY=9%dgjkTUq|s zL24JNcXA~e{fRP8F-qg$!C#Qd?x!NKJU>ayl~6*DF>i?dv22_3AF8l z1{7#w?t>9I#Yl{eq*1S%4w|{5P6{@r&hjdN{(4G-3h|ZS)SDOBT*I-JlnL`2uUgr~ zVI+*HFW^=(J?>&7Jnb(s!h6$purZ0Ge%Im`PCX3oW5%Ua@h1&GXvCH`sV^=hFV9E% zH8$sO5L4brSdpt!+Y16Z9NI}IqMP^=o4x>v(`=4eo;s}8EteKAmGbHIb}uQ}xk^-0 zbd)M{SHxZI@dT79wzOy?^IFgN`JX#)0CFJV)?CPIKfk8>tJ2p1T7z}EB=_{URMyj` zsv4GDb_Jf|Irfbj=IKt~SCUdbrepjQ%43<0o>jjf2H$(Zkx~=2i;sM$ehahzY+)u=N408OKJw*d{a*j2lV`x>io@TKa3>WKnXA2 zPOlt+{qWgG2;uCb=zvLWQz z>;7Swr&+3U&i?PE;bpfcOF^G&x&fk=*_?9VmfNVanmX_KoO|Nt0;A`i7ym~*I?ZTy zu};kZjo#ZX6LfA^XChg%Tkb1uv~a+pYa269;3}RY5oPP^i@`vIhn12bVP@hIgr@Ri zl>ReQ2S)29S?r9{jqPNhP^2#?W)p)#<;f?VHv`v+!YUVo_LP2=1Xd#=ezKXq4Yb%G zA4d-!N6RiWZKb(}q5rX>g_i-gH>Uyl?m!8OH7vkzs2gyPtY9R;IS}2ew?EV|Z2BL1 zKxC~5W+*OA#cn2>#n~TmNB--%R|(tzS%BqfX!A?Y1n>I|QRhvyXVd&rD1-g?>b8f`##aPk8#{h5kZV@NXzB+Gax=) z@_QN>-v#Puxj}5|c50+R7s{@!2fxr?4&?H@$X;0iAu1KHnJ)ZK3y`c0m1=b9>cw+T zur=YM%_}09^ifgTQV%Vnbu9W6`9MbzC?V9I4l1RJ*S|7>J4QZY0z*Ama76}kQ(k|R z0r??^0h(Q$zUp%4#?TS<9>c4!;Nvb7QlGa3#w_Mk~f!1(72L6t`R}*>OVW* z-gO9m?<#S^e;-R!!y28esPSg1_jCM(F@|39kld}ptm|4`RGf1+w@7OYU*7&Ve|?@> z&e4`wj=AO1m=`7Ce(3tN@m@;Byq7z{b!{1C_pn0LGM=XO2Bo=;QeEH+3U-}gy%O50P0EuiEhd*olNJ9wEgb^<{;6%`t0f@r7i?d zu2d(amZ0GT9*iz?xni;syt_Ri826uxW4G}r!ABBIow`}&K_DOFOaeTdl2O$0=MF6` zVv0D98R{v9cC^4REZo8Glwx@hMEJX@p^!Riih1BinZa6h=w>M#gyr@oW1$Yz+gQTw z1?h*$uYb-Rn0iDq3Hb<9hoEHbe0FP;&C~H&$bsg27UEnXA~bg zSthuKkbRs|>uFW2Ziib9K0R#U#n5h-%s!L;PD=PTnpIh}+C`R`q()C%0iEycc)8_w za@P5kZb`S(R}EG`*a?Efohol;Q(*0W&IKMuQ@n5$MoB&cCZNf{=I)EaB!+6H9Onyy z`+cq4@D~r4b5azDdr#aCkIddl-IwX^{l#6>Bn2ACCYH`D#bGJAJ|DcV=uwG&HOI&L zCrQWa{ztpNyRQ#T-@!B+r8<4vCWiIxE#37dKPU)?fe|!IB zeDa-FWTMTOIH?>8C#Y@GxMskA10UGthvcL8OIQwB@bdaf#+M@u?G)b%J!xG5?UwLt zR%xwGr>>JBjb+=%!zM`k<~<{b+Tl4HC^2&`%I8Rpqs>wu!dOH)mr|PZe^J<&l$%r{roUZH(3R6 zJ*v=|oR*c9eYi{a-tg4|Yto+uVR z_%fJ4r+VTu3bz6uw$ zZyS6EYdlaq7qrTKg`wxnk5feqsXre}jymwyIHQDah^FYIz%y`swn@)0C~C>$r};b+1e7 zWK86&9dv>|v)&rx5!dp32D?+B^hkYvY%nh_oFEW?L{*uyvZ%WY{IykhcPYP4@utX# z8t6<>R4Y(VCd<(}`+U>zXO^j2K`} zQ1}}h{{yWf$n|alw!ys;sF>xO{7PWq`7aC!>)AhF=nEH~*{;5A)BKLV*LI%if$L23 z*fZ>!4SkY82g!i;pP7rV#?I@b{R5}Umj;uu|}CY6|t zsTw?9A1#ai_tiCgZQfTE!w-0;5RKlvnxoELzlCQilyeGm4bKX1cL}lNL>EO>0Mzzv z0Yf3CY3U(PAnc_u=jT@yEL4W!xQa&8UL_^xEK0 zwOIbd(wk~UaVv~?UCeRRQaV`e=D>F=mcZ+o>Oe3Q`VyUE^tv>jNBy0y6Kc@(ln+Y5KIZd@9J$ zqIYPYEUCU6_`r^h9JUjDZEG3{KRjf8cJGmz2+`?;+>1urg-O|<##_{j|{v#el_ zTF7McC4S67h1P_t*3@HkZY96>yXlq>5NZYJq;BZx5uxm1HXWRGG$OiRKh$1+BegFV zL|N9^sV`M|NBl;M0sN!K|EkeA!4ma!9t<2#HE9BtX3 zdhT1Bl5fVzY}77BVV0lcnNcT;KB~1#j3Z^oF&tLW6X*cGfWu=iRppt1wO}bwQg@;- z(@mq5JWzt1>~87#n_?WdmVTWJ+Z5FQ&uOIo(HPiWO6T;vQ{I~zbroJSx0rv!KyL@? zW>zVEWpo9sks)jdZWF^i`J(hSXR|5I6CUuzgyA4j$}l@*7gaGG;WO{y5>?6 zB4s_X-8nz*b>b>b?K=)%JIO!ac&6%I&0%QQYz3mfqMe_*=GvhLT1f6tO3r+|QQ^$> za#k!uxlk$4U0|O_otITu%9|K&JW<}Tr|U^Cp}c&V{0+-Sm*d$(aN*T-Lxut^pfSbM*JA4`GK zNE#FNTe5wJAS1>K7A`lpXUJ<=62GSj3=Cg9#3bu0!vQQgXP{(h<>=# zA>pS-*75RC~AVM<8+lGDIw@MaeU?8 zcLiKfU~ty^g=zB(qYSxZuH~Su)_$EtqN=uQil*Z7gX<>C;pK6Y)40W+%p85ChUZ8} z_rX>k!uA7@9|>0j^@8~yOavGS6>@!7qN`@RSj+nj5#9+zG%83ds5t7@HMfZvlXkp6 zdM~bnouqJ0kRhA^vW&c*0-_9G7k@&A%#q(ccwCBuT4!8Zzew@e` zO~`ZzO?3prhU-29yp_E#gc#VvVYa3-0Vi=Z8Go<%iP{~eK8oD+n02y_>d{w-G8(su z78zJcjrJq;z+Jnl3I;cAQ5Dz7nJ3HN2CI4|RRc-grip{}?@-o!nH)AQJqqHImH(n_ zTG>tC_R~LiK~(C;IwtZA@TXPjBR3kGQ$Z942`!D}ppcB1Lj>r`4})?Ua$)r|(~e>; z^sN{RhO9u#Ta5z45nW>eDkh7zRXh`}YzrBvqq(CGs+k;i4u@T1g;9fm-8srZZS5M7 zU{0aiB!%^*p^VpFpC>#Zw<=;p=E;7TixaN9o!XG4Sit^z)X^^d#&f~Gc~eY-K(tNy zT>_%yet$+f2Rk(JxkLw)z3gH+$Qvl?1_9ia3$!^^#T0X?KZ6^z`2Ghl@Hy5ULk~XB zH5C=`ekG-J^gbXL7dQ+t#zE)ygC-NZ6s`n$yvgky;9xB3zxFxtv;?y=h94TFeNw$- zxmAEnmj->;=o_}ZqU4gWgc^=_{|0eqWO!7L?Axw4CDwC{y*wg!X~Zx-X3vRUn5^9? z>7N~blZd%o9j42}DtGmK)V{E?M!rE*1nE0!k8bMDox0LXg}#Nx67hmjzI4H9D{W^! z^Y`GeYk_*vQ) zO7`FnzHyJ#?;UR(FVKjTJQd0Z)wM$-x7DGrrTvhoDqWBs)v0#2RwtL0042)P#mqed z%GoHf`Ag#+9S%7YE6;z`$?^wlKQ2@%o$*0|kOXjmY~O~DqRDm)6ANn+-(}TM^@x!P zK7S$nL~+l3%(v!n!##d6X+hD&L(~ecve{3p7Bd=&rMzwk8xU?INkoI6i&beYT0&w>Y}E8 z{I9aBS6{*~ra7L+602*|#joT3T;|8_`JYs*r5N0C5v7Rvj%v^Hp1tZCaKyB@FSM|X zg3aJJq)B}~(|5Bkc6fRY*-OUXxajO*nLcn>6Z5W|O2Ic;lSs!kbbMz~dWHFe?4~7~Y|{}ZSa>v1_)RRCFK!*2_%`FGCRwii#=4~bdyZPzLCXPbg~sEx zl5Fy@_w1SQCklND?zfBzLEsjSO2~2X#@dpY^e-Q~yV0M`f7^=5Td`bja?MB6C>V@t zoM~9#`;iKpn*}%B-!7d6(cBgWYn#d?oC`sONiELQ8hZMlkk0&{O*dc6h_xs?8xX}= z)vLQIa!~t*rb_glUjGE_mYhrFg7Dtz~dcL|4)=Ur^GW{WuAjgL?eE7Ws%T!7t7RPq^m6~pw86Yo3BUkGDV~I^EpjB9=J?@LNiRRr%QlAcwg1uMR5Q)Zq*PFqy>#M~B2|-S7ps$fHGt+CpzYr;VRfZ~N=R+D zhPSV^Pavk&z^5O54kR#l&bYS(~39Xbj+F;i;uD9r~PX}-9&b%R870Tptz{F>L<^*X)n-qC_-l=)Q-ETI^St%vgD)0RIY*K`Z zXJQa%>UTQ^)0Rg^9^!(1t~p`c)0l7np&Jkn+B3id)RZw zDxx#;;8)Y!bSpk|3(H5vf(<`y9Wv=2-&TM5Re*^|1#-~F4P|)H(j+<{)cyi&T=Hb* z+v0y<0e5;O^J)1yx<~>2SHUl%@VEQiKASKlRT-fLuHFrm=^2Los@`^IptBE5{~C0E zDvxdoFh5!i#0k?j*BL(dQq&(Qhf(v+EDwY^*mJP(Uc;`m=v5DalNBevZQ4py%^ksa zLHIuN3Jxi&=r9n5bL)Y{mx}vwFw3n<9&qj9V$Y6E=JI`jj0ZB&zXYUkrlRt&>FwP~ z6&v15jnSmZHPzcY2x04dtMK|?FP_w#cjQZZuy3w0p=I(b(`(2<4Hx)fW@_ptKp5vc zdqoD@IzSD|Kd0} zj?!htb14dihiR?`&Sd7o>Q(mm@=WJ`o!~6khkU8=b`_Jh3x!d2&9NTq6FHBqSRS%h z^FEHR1Icv8563%<#~&>q9_Y2PcNZ{)FHp!B%$mYlPv@K0dzG3@t1+f@~rXS=7z~|O;xs0Vv4KKrY z^bFyuSBJC+qAO{x9nK$}2Q90!URPCbmH?1v*WkuCsV_MU#JKG!9)&#Pw*v3I56*A- z>6S#B{Ejhj$chO|uj!uVPcI+(9>zCfcT*D959-3Ra}38*$n=C?{VblBUw*ry5ZZVS z(LvXvfxrDZ_04HOeJJVW028mY!C%jCC4iyKDN7j=09~ z0N!zYo`dR5h!RO(kDTW)%O~wizKz$>fy%&8dq2z<&+4miOV<-uk3W4hU)1uRyM#L4 zH^O%hHr{|NnALT906e;r?inn%!+`$M){dOlQP|`l-*=^nGJDf6>lY8UI}FGhA;FF5WJu1P zL9|W2*-~~YAj!XvR*hehkE+tId4~~@3z%{Ck1`TE(l?t)>rs?5Pt3>h^^L_7o!_2( z^sV(^j$AlLE7>WilwszSSd|DH6{e&2OF3XbcXV3qOY{}^M@mJ; zh-iBRF*j&Bfa#8|ltexv9`qj<73)Kh&J-7V8&Dx#A?Q@0@Rl9M2DEcregPYQ<02T% z15CWZ6+lg3>*r$Ez+!-GuCNyAJO%kXn)*$>i1|fP&}w2iWASdI)znoHES;QJ2o7w# z`aIPz{NrPe=&VaU_pROL7pZZ>9&kr%+-LsU+q34h_xkW4vX-*f>RDq^Hpx1MGnwCkgIb&&7%2$ znzY+=)s+~YjBd5Qkhl8AtW^L9$1{ik3OP?R^uo}|#%l#CsF3m`}W8O4@f0q`d6 zLf?Ptk~zBX_-|F@wfC^*wEg*9jJf=3gX6Myj-Hrhk8BJ1zfO$; z@6t(LfRhsFY2}y2bzHv3S#au3C$Mo&SH&2h?En58o2Wikr<}nizO{J9fT3p@#+kLH z?h~*2A^Ym44&YTNSl4)qG~fq{6n2$+x^tkO@R^GZmpEkl->qXt=CG)C>O7|(r461? zC$UD%g(;fsw46euR(WjJz9-(xI0+fuUQJ%|HzY+3PhH2{+2pd!?5+2J!<16VHRP3& zK~|Y=3AU-1JT=WLU)Y}&dIX%WRw#+7M3Fm!p#%^a` z;}jt7nw?($Oqj0y?5pQ4=i;)sE{l`Wk+W7Kzkdt-dTb7K#J*n5Ucu!8)ws>yGU()8 z5}yL}RStvaf8BW&n73YiSt`7f13A%_52fkAs^>O0Z00|)@`!S_>F|m?N90Bf_LSdYlK6;X|BffE$F;zQJ(};=E#@7 z_M>ClVoD+Aa1~McIAiUb!u;{uD#BXDD%-Ui5sYIMFJtE!Z=kHyW7kUe>Cb!omp21S zwm8Pvb%AOl-K&2K{WVAE^S_HJQu4(OEP$IJ`O3=0CO4H2K7ErFDx%gh^dY_mEO)UQ zV@-U8vNw$_6Y~MT^@}2KkVjm(gopIn8?!G34YGbw)u==dX}CTR5^h6dv!a5!w*VE7@bhxN$shPT?$(n z{$B~&^yk4CB>_c`^5X(JnB?c%phEYks@qFBxTuk7JT)H^=S{HFJBZk_N&IH9pcmJ~6!?Cs&cyUM)KouGdiZobT{@uaD2lD~2C zX_Kck;!q1Ho~}jVNA|x&NWOW4$luN_O*`!7R(Wcsf3D|xsiMvo^C3!gQ??tRJWC zh0XH5W1m?xfg&oxvpawoFTZQ*eU$^4ZAqyY>C?wc+MO*_CM9#xiN_igv1ic1LTcX1 z90mnf{rYoO+*$H+_Y)6$p-e|hlnEW-`nr4dCBGs}+Bs{poqPmv4|3HDcbOMR9XjZE zFj!B5dTtMtW_$u`eK~2b(??0URt!tq9vZEUy;6=E)zV{dkBM6+W|-zQ_nxaR{tEr@ zmq|PkD|68R!N>7D7T~(vNwo=yn;i#d%1WM&c9QHT^{mc~C{~VLl!<)_J)0G?`40gT)6*;dTcq*6Losl>h zfk};Q`J+uZ>}#F=7@*XBoim%`@S z&?}{qyUdJ9kRr#w=Kvk-0TW>R+NXaEQl(cFiNuFz$=I$JOSV|7OYbvX*Zzb5o9p4- z@IhU*OX2z}56$UM)Zue8yu=>vRkx3;*AF{Xi7653HFB;;9|{Sqc+-up6f_?fk`|Gc9d2?^0vC$qKwIk%3cleDMgYP@Hc zu~b!j3k0G_#jAv))hiDlIcs(SwYZT$HiwSST&((Ir$J+^o2miMg-gpCpaf)Yt;%#D zDb;Nz4alb3u9*rvx)=Ye&??~{T@nsxpnG8vWQciOs47|HJM>#4KU5}%Jb6*YeywfN z0qB!;3t@Wvdeuzk8{IMl?6)Im@o~9}QSowm6JhGI`GpD6 zXybc#g%#bGy!#EmRn^lNOWnqwFcqSik{nqOZRyIJwYy4UaGUo>XFfRX8(oPBdkHo! zM*qggPgu{Z_ORiCL~BpT+*&(1)BCzr`!fRn7fT{*2k~{br=Uh1fU>N|E_ZH_N2bv@ZH(=4 z$fnQOqzngZ8~d#D#oLcQ&)6Atc)6DPuZO9x#0VbH&=?#3xYtwX)}fsNrn8N_BZwn3e31F{Q0sB%$` zcCgd~3x8bzNzp;&i(NE}+CUS8WYYZLp-xPpY5Pv6U(}-geY&0#I&$fN(Po zSk&RT2yNzprA=S;n{b#7*yO&VGuGOno|Lw$4p`KsEXILAbj?}gm{*S6U^Qu~79U{2 z%NTPh=r_jLf2?o3c6p$zGCp)Ud7T7TTyaI|Ak;dVZ=;c$yhyejI0vf9Kv}hwg+61b zYfumMmd69^ST!`8xdl+_Qa{ExSL3BXaf3?LiK3kE)@9pPXM3L9Kxq>y)h3#s)Kp`g zW2oG6Xm5V<>(E!&Ub86+W%EddeA7#n)gJ}@#&%_>{eeoy_QpfCK|P*Le-wop)7hB@ z@706{SY2CXmDLM*ZR@au>a^=;Y?HNZf_8lyj2*)mW&MO50?Mn(HaN}Ra_HA|jc<88 zdGu2+U5ENHS{`YGF=O6-HhG=uyAFNEZOD&x@(s!Y zn;X=Vp!!DUs^~hhhU(vPL+aXU>S`b_!j0O@?{R&*aUrymu|vgHgk>r^%FzZEVR#N(|nmS%X3!@N$o^L z2g_oI?396HSTs;phL~?-zs3V|>yp4yHw?pKV4$|S^t?RIw(N}Ft|H3v(gx*qc*bit z)&nc=syX&kr`#w|>Qx6w7H~|=Wew&A7QPFoLztFdGt_ZV2dF9sMp>u;R&uL{dMl<~ zS;#j4ja`XdD3dX!)h^$lF3sfw=1C;YeR-9fY_X+KYIPih@r^ph)hUXPHlWlZFPdEn zND7**(W1P>4wmuF)p)1#%y;Lhgf5`^X-ie9t&?YinpeQgw~WUkb3y?bsP>KY-F-U?l}m^!lwB=7Kq7$98#hI(9;P zrjZmlq4a4`Der_A#7ELUwi29yaExk0rjTRtWfP#Y-9+yMbgSXdj}G#8sx0FniO19m&=KPU9>TRP~# z!oM*vuAyWqk~97Whh#6{rx zp6uURvu5_pnK{q%Tw83P|E#zFTKi@8%sF%J@xAkgDgGSjioP!5r4CD;wlJu4)4CWy z^r>F}iWoekE?*b`f)8lMcpxvr;zXbyTmUzUn{$i-QKM5p2O4C5lRNrO-S;5>l)aGN zy`#1C1ByWTG+^;-iz^R8qEJ7AlQO21>!ND!!T6rv93U13azK#sD5qU$Kr9|G-;LZT zKpPvZ?TXoF!3_+|^@_8V#UK5yN%iX-$JhtaftZ(lw%$s1b$$V}Zd~X9VlJiYcbwWs z03sw;MLrN?Xj5~Z6?e^QzDgkaEWbQp%3K5xJUHL@<}A$XwUMtbV5wgf^p{?8{uZbl zB_#u4s;fZSkb~9_=`(&)96h{l^MP2Tt$)vU!GXBLjd5T!&vYRAa4w=Aj6Ly^0mTeC zvoR<@+_h?RI3B~0`A2zjwt&oWLy4-nAUq>))aQ4A zU~LW~aT5x&;!YljNkw3DfQ&g&bA%_=K}ywzg)vvgEF_3HrZTJ~eU zfx9U#aFuCLS0OhGf^=h z>W&0pHi0?rIthc0wZ55C`Ym7GNaWPT0>s7G4jL@=_b5Pji!N^Pf`to$~n*#`S$CE5!z_-kq86_&^$x<6H+2BZxs&yufk zSkqcSY+`kvvy{e!;}(uxyEcDu+AW(ku1f)eB6kF&KZ3&n*8X@0Fmd373d91!v6KkJ zTfdWpHlD$rx&`qd4aKwbH{|_K}soc0l?zWS#V)Q*_Wgf7jn!2<^|Sw z3G&A{taG<Q5>Z_WdUg8 z$A&5^L)ID`AOO4eQG*TPgHihvF%L znXX;X2^sU`0WrTeG|2einpNv)D21ERI&K)qi^4c8HGue$pFCLn@@@(s*3v$2no~R= z)SKoyP64d?G+zKw@3sfXJRs`cHvfq4n2b5*(zOpS0LTFVtCK$_x2fq9kU4e&OwmOE z^Q2(W7e3 z$E~aj!2l3-?W6SuncLMHy{spIxsG=0tz=hsUvRs=#v+ipV;`dq%;(y?F{ZtRaiJej z+zLSSS8H3f#v-FnT^wEj27$=;a#{kKLwB-OX4!J^~Qj~7LkUT*53|QOd0a0@%O^Pv9$o?Uv z^8^gt8s?hh7?t)RN>?619x#tpd*+`2E?5E+Fiw+qbuP6 z&TN>;Gp7v=G7f|nQd-lm#oWAgA?OppTnm`{G$x9#^u+4(p;W1Fi}EW4WPNoZUkTY4 z?91Xn=0o!Ix8D2_p0$7&Go;ifM=8`HYp9O^;`6?11GKRLQFr7Y>A<{(tv5(}%ZK#C zxhyVZOdD8@0Wt4st`EiDyQtv2ulO2N@(Azv!i`rb zTUn@_vPWr5+Xo{blJ@G1KDkHmfl5G5pz^A&dF4|tI0jL+Ql3(OL3c-^@{affCVO(1&%(srZ&{mGv^Nn%!;wTHFU%g;lz4|SClV?zvd;invwEGET;hoNMD;6Jtsz5n`QfFD5l5=3A7}w=pK;0AN zMpYj%PEx%va;)Rs#+bb;Irabm5CBO;K~!~wcNxg>$%WMA3Vj0dqlLOK2E;;WTT7@5 z<6D$p>3ob=jyj--uLB*Uyg~$~10+uqh`XzGyqL^$jzEm9O|9xEC)X^>2ZYZWK-6u$ z0jpX-_vQf@i+tPy*qDV`(CwxE9vjqCYM6K1_9Ux3057 zo0@s5B>~O2U{vOJ01Zh6?E)|t*9U2<7b*{k6X9ZrHZ>PeXu^5E{t?zI+sy~WM)P8_ z4iNpw8_{|L`tkTBABZ`rA2BOt|LSg7xsbW)Bjkwkm0r?u8*}rkTh}!b+Q#Gx^%43C z<=0kMT0fFuL4DI|`a0lFeqCrSsTUtK=rXF+X?LoN<1JYzuAg0(bO zfXsEn`wZco-KoM7bDE$EG>+0$SiyxMY*}i}n&njUsfWb=DvAHI7 z@m3w9#*_uu%(!#X2g^mUz-|NlctVEE3i_q%GeXLj5|xlIIv;vA4O9F?nNbYmc$p>Nl@-Y7IC2`|T7LLATqw0da21 z3zXX88DO@$0t6XSc30S=90%tD*Bx&3%NKh-{lb_~F94;^{a)g@K^+6E^MJ^QlylJo zW$eSvuYI&}En!?Kwz@VD7q2xOBP`>9)Qi`KCRYHFKXTODHLW;G0oj*n+RFO{Fpp_l zYiqX)4Y&}!F0{!lGyV@9-}Nd#?(JpF(s7D%#7Ceqlm>t`Qx7GzRcjUC* zv_)6rHR{#B7h?p6!^j)lp+fehAP6i&j4Q z!0huxJD%Dw@`SvCHc)1KUM(LGo4@sDtuC&8nLCoSff&=#y2ufg2b3Clv(c5}IWI^a z5WI?Y+O;pa)(}O8tW&nqxLkEH>e{s~kh!o(;~yq~8CzOI!A$x**4V4YL-tM*On6tD7?&U5S62I73m(H5mwPT3WM%mH-W z7si_RRiN4TRlkv?Y+<*lohEr%T)Y5kfM5r0KCzZ`^BWi1djJmKHh~6-c1^PnIOZC+ zgk!3^lp99hh~kwGvZg25+=@f`-bZs}~S+E+-Hwqzq#?SWh@7 zrF{VXgrDX*z}XrD5qDm~Tx$qx=xe?TgJ z4sy&*xgFQZ&_%2+fGl#(*YDy%E{OU$>-HWX78MpbRY*KQY@@exI}4>y%D>y-aULaw24KoPLc2w=4~))sPr$k{u<5_3$&f%IDghM_Mv_W#V^h!+n6XWTr~nTNVMig_kvj7y^~Lh3&*Q%%+6dSB@mscyYO4? zO>JP8YFz>QabWJh3IL_X9bh)P0tCOS^ShOsv*x3};4e$n_hS0DfZVvi9I7?(u5TR! z&PTb$=!dKUDdid$>PIkcKh6o5=PDmioUKVX`vI1?b%5shfw@lkfo9WILfYzEz;Vp{ z!Zz>7c7Enm9G3d!cfCuO=XxlekNN8N?s-9YVQ3v=_X4n>fAzS355K=y^z*SL?D*dF z*9wjUFm@eanTXuVhs8hofceD87l7hMu$E(0y4iuaD71mtDC!m~slEruet{Sh5SzyZ zA9F{&?X+!NTkKG0mvK=Jb>{^+7X6L`P~8PAG03@FLo#H{y4DepJ|N~R?-GCrTO*Wy z81-(>uRXuM0?^!h{^>su{rd~tM;>`3esSu!Kl#Z|wuc{nIP3=>d@#-{WZRL7)7R>OxzNy7K~5 zXBZzb2R4eFasln?$_L84b3)dXBZ^m2U!lfQwi1YQx#gFAB%RM=k3H7D{N*nP#aiyY zuYdjP?JHmTN_*&`huRN*@Pqc*&we&8fbW0*`|b0e|NPw7=RWtj_O-8lEiNK+qsZnk z`mfyRb|BmwB_$QiFJ8%!tGBzfc$I=BW#HeFY%C z8FD<13y9z2%abopzf$NkRw!FpHH>L1%@NvL=ok7+stX|2L|s^`pk1guWsTDq5c4X% za<7w+n^PX#tOjHboJ;$+)`&JWIOjw9q`1dM>BaPoZ+xRY`skzauK*eI?}B~inP=L! zzy0m@-S2+4z5Vvv?Uh$viM9K}7rxNG_r346PkiDNaZc5}fMDH+{Rr!g^2$5m7J$4N zkhVPUs7eRA2V=rtPWt;DbvraqpHSC7N_Da4-}4pMF&HnDUs*6#Jf(0PO2?*@Bg|2b zKJ9{WbIMlg8==qqEk^&S%PV^;?Q4wNj~xBN*bz?tMy7usZanpS@o_;r@qV1+)bGt7 z3r|x{`LC13k7CmSh`TZ@atf{bDWB3h-ue5d?9OTJGoN{+Ki<;Ed}{ysUvIUSUV5qh z<)dG0PyP7E?e1NF`q0{2Z~b?B;e{94H^2GK_MPv1r@isU8|}?E-?ZEeVAkzJqvYzJ zNCJAtr{=~jLXq7t>Of3P{Str(TSs{J0wNcPm*S~lH=lJ3X)6mr%y0Coi{g|%%F>IY)aRIly1vrO)>q>(roA9P z%Cz`>jGKEw`j!VoAkwT`&;jXJ7Zp=#AHn#Dk*9xaI|qFq``E|Zx4!j{?X}llYoGq~ zpSHjH>%Zye?oQ+CPW#A5{-{0tC!cQr^Xp$XKj!$a)x7`yy&k`6Kl|BB&F9rmed<%~ z*=L`PbI@+sR?h&HbNBAu<}WZk(QX+3$_ob$d4UwKq_F}p4?v$Mrf}!Zok|G_!~`I7 zV3FpEHZ;io^$SCQA^@q|9}wfPPr3RemF`6#$~Gpnm5}+6Ql9`6^WWP8Ii_$<2T0Cj zUKFr!ff%z1u;|(a#|KMH+ZdoYggnvuJylZDiFvp-#`Yjr0Q1GsvHezm!&J%@k)cYoV{^X|Lt$$$D$zexU}ef*C<)~;y% zBj8ng@4f$RPdxENd;IaoW8XR-Z@AXHZX9s|0Q2M^b&J#m;02)#8QT_y^aIhK@h!}^ zPHj4ga+KEra+8gG;B3rQ2ME=Ls~kw5wqx!C zngMyCY!3Q`Zr4xg9E3V7HGr5CT(zzmo=|=!TXFJm2>TQgO2U|LWC1KO`J$pndd1za z4?OTdd-25=+dJ>P(|-BOU$lRF`Q`Q(fBt9fp$8vmAN}Y@+E4!FC+)RYUu`e^^!fIs zFFqC*@2lN*U-?|{Zofd@d+)vW`s=U9|GH{zSjGfv-7#HBYOS3KgF0)$wFS@@Hcy`M zE#&Acsb0|MNpoWY;nr6O$8>;W^7XLTfov6q;6edQ9u!ZMGwd7aEgz&x*&Phfa7yHa@7lv`DJe<5NiQ! zL%X)?@4onH-`KzU)qljs)_(HIAGMcX{@3>IyYI9g{_yek+u#1C-MPBzA9J2*|K1;h zfA_opYfnA(&+Yeje;;+7j~Bu7&p+S%2UOsAZgNdJyZ{VBPC)XA+Q8+LE3}n+0Af+( zxbdn+arGC<2b6kt^5(!saWSo}+j`1^G02*b(uD?^Ia_`^Xj@*{K+JKQ2FLy_ch3f5 ze2(Aow%)4GuEq-pxjv+?KhwszoMUUmL#4jf+qO=3#S3Ua=z9k^kJZ@Z184breZj&r z|89S{?XI5N`h)J>xRaZAw|^{m{*Bh|z26_+r+=)#nEy~m^_ey9HXxw?2$5@0MW~M` zA1uZ6*@=SzX1e|n24ISJ;hU|$Tm@oaUIf+<=E=Vfd%V#H9NzhXW&@&sbz#16970|H zbpTcr*Th^P>Z^+t=^A5?ug`Ir>LyU?D_OcWWZ&u{H~CeJ2k!KZ-};TDHMnZ65qg{2 zSZ8wGN{haSzjb$wER?rnXJOKvz04pq~Ym?=7E z<3N$)$reBZ4m*`Qm!UQKf8|qJqxI&U3FunBjES^bUl1>!vKA=^GS5xo=SXYnFBW86 zTfa~rp|3WVbzsQ>8Zgq&bjY}ML=mi2`ks_4_G0>g=-WAY!_apd0I_z&hF`DarGD+h zctQI*j9ka7y_Zvd;4ExR7*GoJbC^3~VSxU$kMmoDimKnhYn*vjIV5c z>VRSwa)8KFm*kj~mjFb#av*J;Kyyy5H`l0(cokOz7QXf#tYMDWi(lshVqUP8M|qd} zfvi!vK&dNPHI!l%ykNk7Js{>#sX(di>;jl0lTrgHIl1S8`dwsU46?X>?UFsAn{+{! z>z5}Oh!YMOuRKV*?KC-VG{_45TQyGva}&Ii~XuXzG84rtD>zIA}e z*A~Fc85LOjt8drkQr;eP3B3RS0aQsuK~$w0K&)r$%^F?ASd;=-+4^pSs(F(fxdr)v zVi&-DjSD!d2gDixQ4`j(^|rbIYLF#+JQB6lN0iT#MsEOkB<7oS3g#KD= z0OvXoW0O!h>VV=#fPJp@V3|)91ENo$T(bnu?%Hi?z?=sJ&$+_d4{5iZ=9otJ{{sL3 n|Np)QqwN3y00v1!K~w_(9(MfNh}X(n00000NkvXXu0mjf)9ABX literal 0 HcmV?d00001 diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/android-chrome-512x512.png b/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..eb1b8435c118175b9c8d6399386e9701866a80a9 GIT binary patch literal 225933 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
<_XU%B=UB!1XGNYdO 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⩔m)lq zeXad1k}B?9b&dh}V`{`BlA-XO@mXEQt=b(LcG)_Xxrh zo7pzdvuyDobOPW}HDQ7CHN(ig|jaI3{a<1vOF&b1ikpEsVfUk}UrlxH^6{#z$tw)@eBB&_yY z{$&60oB8F(di2)0-)?=qXi*Bx0VGg8yiKe|qUpEgYOYS8fA2-@JB_WqGdL@}%wyJj zw*QXxn^tI$i;vX>xx53FBKSg50ptkemfyw>xMQ0$VLhQ6sR+$5*qk8q52>yiWA8%; zlhK;eRx-Dz_=!im--w@PH7MIVi7#iAP--WD`hdcKbAG|^0o8YSZp)Gx=3My1eq01i zHFb!+H zcW`jPxw{*0aZt0_jNV`UF4hNV8ZAXUQ&h@V>l!y%gNvCn_~^D9&QpVjv8M^3OWf;o=zIh~wl3d|hY z)BMx@@w2@^Y_cfReNZH$qF(x8iFMeC67FLn*a3A%5m~oY3m%)UK7LtZ`N)8Jf(3|S zM$hQMRAig7q`${tGej|o3obfBQgs7jPql@F*sD0Lh>S3zyHOy$P*E6rebxo$toRx;L!)kM(NR$@z`8JC37P`0K?{bhQykdavb`XBw6 z+#gE;N_i@vAER^P{GKRKm6Mt&^pZ-*z2I;K5ti%VuR(pr5uj!SkAG%MgbYHWf|{%n zo0y+p;eg+FcDZ)lbZnRWYBMB!X#I8D(7GUVb}utRBxlc&?YwQUZWX>MAMq`8JD^P` zym%coyYWg}6}N^gSu`8ND3m??IK(O)Qo`)7t*gxLew?*!UCL~&3gwJNI0k(+Pzzm3 z+ox^rx9xJ4*?X>2JDBC988Bj$G zML2q5920g8)NiE^od9fq#Qu|12$=T9dU9pd$X*PSI!4y|>~jg7ECZSY3vW&U1N+&t ziiyeU#Mc7og1Z)=Jgzt=*V)AOgpai%ufAkk4u)sF#Fg{~T>!8K)O#RF`g$REu_4hO zD+*!%)@8o+e?MX5DE|3aPoMNlTG(MiX0p&qS>vFO(sIudq(J0uIkYA?-uer*lT*#e}O|pRDBmKqiN!hrNF1Cn&(+P&Oo_3iB$=LjnG)yi19Gt6fwA*WG|jBuKb%|` z(WK8Vf7pAlU+ZkY|2<*{ws`uWFiBzG4CJ?$us<17U-gQ4>YH=L5o)L57Rm7XRvMV& zgWWb-Pu?HzSkA8M?H_Zd^3v6b*2RIg8YxXy7I*0j)_X$89KO{Dpn_AZ$cCcHGyP41 ze`*BYJ+(Brsu&FevHBSvU!&YP|G~~TWW|A~Lw9(c@J&fQS$*;=%K$1@Mup{%=$Pk5 ze+>I`?pMRoNx(i{fNOfhQBF@2M=gMVaJ#5q)9p8NkTiEHyv(aqQ2S z-823PSBf(!uP8U}eJ)2Y>i{oaQ+N$)u!DX@Jg3Tp6fCaSnI(h7cvF#EyjKpZ{UK~U zU3sok?m=Z1s8FWBqsUF*x|+ZKb3>42V<7DJ=C3Q18&b`EN`@~-Pga}Z=Yr{!^3=p@ z{+4$!Nb0}&PXkDl3<6Gg8F{d!sJq{6siC-{=hy6HvGKBi#|O^C_*z#cI!8d3cp7b7 zVy!aHYE`*mnC@$`YUL)yeY*<^t&o>+LK~1#1_gVsSfl%yGg}2aXY~xt*cO}_7O@_$ z3Stela&TDqenj|}&~|93%ijI{mzkN`51p(c5;4-4k+y9sgZ$8+F`-X)12B65i+Zgb zy?Nta5etJm;Y9{Edmj%L*N-jlig6iIKO212-(K-s>|vF)i!dJUBc}}YMQ8M)#$K)b z+TAUDxmvt(NoV~Xej;bgFyhHyxXAzfoL+JHWSLI)7rQb1e`?81odJ7Y9$q`!- Q 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 f0tx-4|6Bh9oTO&> literal 0 HcmV?d00001 diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/apple-touch-icon.png b/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e8f176242f3fc784b89c3eeed5a8e0baec6a2026 GIT binary patch literal 37679 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 h00006Nkl+Jvl literal 0 HcmV?d00001 diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/favicon-16x16.png b/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..706249553ff606949c8a2d6dbdc522756edc591d GIT binary patch literal 600 zcmV-e0;m0nP)nudb1+1c0iKetop&q1&KTXb9rn3s9 zOQoHag*KwOLPQe;e?a0sXWnM__V&1e@4cCMGvE8(?3!FIXY={IL(>k&<1)DDP$vl8}LtqgfwoCs(j~s)TB_Dnu6RmoMV>^{Y6Y({_2Z z%tHGj1safq-hOuVI5xKim~3yOUazBEF5|)Gb4(_m@qQZ23t}Xt9y_Ka#e@HFvwjc7 z;xSYz6|`C{^!t6h9=*bwx9{-R(#a?38W%%!K8*EoZtVolT)Ki48h(8H zg5UF?NiPZ!5h&rxAs;vD_wnrM6O6}WOs7)}1_QL)ZLDwH!G%%*Cg2Eel+}=QL-J(0 zrNR*$US7s%uq8)8x7)>VI7Fw@!NW(7Q8<51YT*=NVUYs;H$Hs*g}b*l@bUW}cRU)6 z&}=rP$)eZm;YI%?Xk^Ut1;{{((YKI<4`T9@_#~Lipd3SDG19DmD@l+6A_ZiSNc8Lq z^%quXQZH7x8E7H|$;Ga zh!6Qt_5oo8KIzL$3@M`yGht+4jhdxMHgoSet>4=Be2nwZ=KRk3+-t49_TJ~-sJpv6 zDWa#RClUTn*VWZEZ(izutG=(VFY`;|idL&t*GD8k(h)@wG{_*6gmh$(#iKEbJjsR@ z?k|C)24RJxuH^7_mpyYPH6CQcWuI zWC$P~83K!!pCFzLdNNRL$E`LmfHa51V3lj4pyUE6JwVcd#!w)VE~Pw4_aOt7Q45h> zyg*VU^Ya4aK+=_NwU=~)vHpk`NV=>Sne(!xkWJe`67_mrqJ!kT98!7#WN4=XNGYVe zXo{j|L((;N$Pl;PderEMGBznVZDFv9=5#twO6EjiEe#2qkOfQivBMlVN-84XwVt zQo(C)zmJz+SuYoC+qMliZrs3{HEXbPzWdT+Ia{wH5TcTW`?Hf%s!Tbr+b$BrF-u!e_+F)%RT z#a#RQFMP3g8-BYU<9e;hyG0TtEn0Z=eIN<%07*Bh2my^Dp0@YhiSY0;&4PI^l+!yPMv5XdZBZ0sMNbI>Pp?N;cMpypJ&LKR zDeT#^#{<6k<`7Cz6Si*I3awuApL%u>ZFjB0U3OzytO1HG$fo3^B1?jI#tT4Wq>iv- zQ!>B4MKPLNE4KF=ef7I`?Q+ktu`!%Hc@oQ(EyqV6?ZN8Rt8w+}Rlk;M@rKPiv7{}+ zl9oD3jf9F#(vyl86wC`CDc+4boe~x|+3KrJc;vY^(An9AZ;pP6>8VL5fhOcR#f0fWS!Uh>~Xs(Ib;{`~PLC~kT*|b`H+4lbVSBFrK z>-g@|-DtKLYNPy5oH&8;@o`wyP)80OI)vHTS!~|C85%FXy%j%R??f$@ltVd43uG$s zwxGyYKnA5q+4lav?g&Zy5**$)jJ+Scjtf7ZMGR)X@;iL^Ft%*@5V{cxrtTd-ejJ-N zZNeveM{wKi%K>vFX@uEaGzLk``w+o~u5CRgF5ZlA>DOs|``Lc{@YNSMcQrvRF$a5; z3?L33JcxZG`!GE{?c1U5$fm+ZMn*6(G2wdHkWDal-s1(xKr%Djv;uy*42=GfVC)hx zIs;srE18{HKok5?LNeErAr47jadUGs)ESZ>b6D%4;Xg(s#k+$Tbzv?B;;0$3M(eOc zL4z4dLX%0lKe;eD8bgW2%3qYENMjz1BHseoNLNGBB>2^H#$K`+@eYVDLV7aH5uZXn zI@CoOcEu%Yk%BLa43o*6k~yylB&}kK&7cG%7wA(5C07?j)diAn(mX+e7mx~4kikh4 zG+DY7x1JUG$d=AesdQwBqvX=(fEA2vcmX6$Nmu5M$$~8)>02O2GUp|0o@fzdh)D*T z+7kaa6|R&Qpp_=t5c43J6+;G|q#ryq1{so*xm_{HT$U?Ik*sL_hv>Cm)XvUsR4N^A z^IIT}49;q^ib+wF*i-hJx?7V;MTTy8Qn{xm@vG5`Po07*qoM6N<$f>l3e7XSbN literal 0 HcmV?d00001 diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/favicon.ico b/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bf4991d6df3f15d1510c0b04aedeeabb81fb02f6 GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/site.webmanifest b/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/site.webmanifest new file mode 100644 index 0000000..1dd9112 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/public/assets/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/App.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/App.tsx new file mode 100644 index 0000000..a7d82d6 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/App.tsx @@ -0,0 +1,36 @@ +// =========================== +// ©AngelaMos | 2026 +// App.tsx +// =========================== + +import { QueryClientProvider } from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import { RouterProvider } from 'react-router-dom' +import { Toaster } from 'sonner' + +import { queryClient } from '@/core/api' +import { router } from '@/core/app/routers' +import '@/core/app/toast.module.scss' + +export default function App(): React.ReactElement { + return ( + +
+ + +
+ +
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/hooks/index.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/hooks/index.ts new file mode 100644 index 0000000..383e6e6 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/hooks/index.ts @@ -0,0 +1,48 @@ +// =================== +// © AngelaMos | 2026 +// index.ts +// =================== + +import { useMutation, useQuery } from '@tanstack/react-query' +import type { AxiosError } from 'axios' +import { API_ENDPOINTS, QUERY_KEYS, UPLOAD_TIMEOUT_MS } from '@/config' +import { apiClient } from '@/core/api' +import { transformAxiosError } from '@/core/api/errors' +import { AnalysisResponseSchema, UploadResponseSchema } from '../schemas' +import type { ApiErrorBody, UploadResponse } from '../types' + +export function useUpload() { + return useMutation< + UploadResponse, + ReturnType, + File + >({ + mutationFn: async (file: File) => { + const form = new FormData() + form.append('file', file) + + const { data } = await apiClient.post(API_ENDPOINTS.UPLOAD, form, { + headers: { 'Content-Type': 'multipart/form-data' }, + timeout: UPLOAD_TIMEOUT_MS, + }) + + return UploadResponseSchema.parse(data) + }, + onError: (error) => { + return transformAxiosError(error as unknown as AxiosError) + }, + }) +} + +export function useAnalysis(slug: string) { + return useQuery({ + queryKey: QUERY_KEYS.ANALYSIS.BY_SLUG(slug), + queryFn: async () => { + const { data } = await apiClient.get(API_ENDPOINTS.ANALYSIS(slug)) + return AnalysisResponseSchema.parse(data) + }, + enabled: slug.length > 0, + staleTime: Infinity, + refetchOnWindowFocus: false, + }) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/index.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/index.ts new file mode 100644 index 0000000..8c2d64e --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/index.ts @@ -0,0 +1,8 @@ +// =================== +// © AngelaMos | 2026 +// index.ts +// =================== + +export * from './hooks' +export * from './schemas' +export * from './types' diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/schemas.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/schemas.ts new file mode 100644 index 0000000..ad691b0 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/schemas.ts @@ -0,0 +1,397 @@ +// =================== +// © AngelaMos | 2026 +// schemas.ts +// =================== + +import { z } from 'zod' + +export const BinaryFormatSchema = z.enum(['Elf', 'Pe', 'MachO']) + +export const ArchitectureSchema = z.union([ + z.literal(['X86', 'X86_64', 'Arm', 'Aarch64']), + z.object({ Other: z.string() }), +]) + +export const EndiannessSchema = z.enum(['Little', 'Big']) + +export const RiskLevelSchema = z.enum([ + 'Benign', + 'Low', + 'Medium', + 'High', + 'Critical', +]) + +export const SeveritySchema = z.enum(['Low', 'Medium', 'High', 'Critical']) + +export const StringEncodingSchema = z.enum(['Ascii', 'Utf8', 'Utf16Le']) + +export const StringCategorySchema = z.enum([ + 'Url', + 'IpAddress', + 'FilePath', + 'RegistryKey', + 'ShellCommand', + 'CryptoWallet', + 'Email', + 'SuspiciousApi', + 'PackerSignature', + 'DebugArtifact', + 'AntiAnalysis', + 'PersistencePath', + 'EncodedData', + 'Generic', +]) + +export const EntropyClassificationSchema = z.enum([ + 'Plaintext', + 'NativeCode', + 'Compressed', + 'Packed', + 'Encrypted', +]) + +export const EntropyFlagSchema = z.enum([ + 'HighEntropy', + 'HighVirtualToRawRatio', + 'EmptyRawData', + 'Rwx', + 'PackerSectionName', +]) + +export const FlowControlTypeSchema = z.enum([ + 'Next', + 'Branch', + 'ConditionalBranch', + 'Call', + 'Return', + 'Interrupt', +]) + +export const CfgEdgeTypeSchema = z.enum([ + 'Fallthrough', + 'ConditionalTrue', + 'ConditionalFalse', + 'Unconditional', + 'Call', +]) + +export const SectionPermissionsSchema = z.object({ + read: z.boolean(), + write: z.boolean(), + execute: z.boolean(), +}) + +export const SectionInfoSchema = z.object({ + name: z.string(), + virtual_address: z.number(), + virtual_size: z.number(), + raw_offset: z.number(), + raw_size: z.number(), + permissions: SectionPermissionsSchema, + sha256: z.string(), +}) + +export const SegmentInfoSchema = z.object({ + name: z.string().nullable(), + virtual_address: z.number(), + virtual_size: z.number(), + file_offset: z.number(), + file_size: z.number(), + permissions: SectionPermissionsSchema, +}) + +export const FormatAnomalySchema = z.union([ + z.string(), + z.record(z.string(), z.unknown()), +]) + +export const PeDllCharacteristicsSchema = z.object({ + aslr: z.boolean(), + dep: z.boolean(), + cfg: z.boolean(), + no_seh: z.boolean(), + force_integrity: z.boolean(), +}) + +export const PeInfoSchema = z.object({ + image_base: z.number(), + subsystem: z.string(), + dll_characteristics: PeDllCharacteristicsSchema, + timestamp: z.number(), + linker_version: z.string(), + tls_callback_count: z.number(), + has_overlay: z.boolean(), + overlay_size: z.number(), + rich_header_present: z.boolean(), +}) + +export const ElfInfoSchema = z.object({ + os_abi: z.string(), + elf_type: z.string(), + interpreter: z.string().nullable(), + gnu_relro: z.boolean(), + bind_now: z.boolean(), + stack_executable: z.boolean(), + needed_libraries: z.array(z.string()), +}) + +export const MachOInfoSchema = z.object({ + file_type: z.string(), + cpu_subtype: z.string(), + is_universal: z.boolean(), + has_code_signature: z.boolean(), + min_os_version: z.string().nullable(), + sdk_version: z.string().nullable(), + dylibs: z.array(z.string()), + has_function_starts: z.boolean(), +}) + +export const FormatResultSchema = z.object({ + format: BinaryFormatSchema, + architecture: ArchitectureSchema, + bits: z.number(), + endianness: EndiannessSchema, + entry_point: z.number(), + is_stripped: z.boolean(), + is_pie: z.boolean(), + has_debug_info: z.boolean(), + sections: z.array(SectionInfoSchema), + segments: z.array(SegmentInfoSchema), + anomalies: z.array(FormatAnomalySchema), + pe_info: PeInfoSchema.nullable(), + elf_info: ElfInfoSchema.nullable(), + macho_info: MachOInfoSchema.nullable(), + function_hints: z.array(z.number()).default([]), +}) + +export const ImportEntrySchema = z.object({ + library: z.string(), + function: z.string(), + address: z.number().nullable(), + ordinal: z.number().nullable(), + is_suspicious: z.boolean(), + threat_tags: z.array(z.string()), +}) + +export const ExportEntrySchema = z.object({ + name: z.string().nullable(), + address: z.number(), + ordinal: z.number().nullable(), + is_forwarded: z.boolean(), + forward_target: z.string().nullable(), +}) + +export const SuspiciousCombinationSchema = z.object({ + name: z.string(), + description: z.string(), + apis: z.array(z.string()), + mitre_id: z.string(), + severity: SeveritySchema, +}) + +export const ImportMitreMappingSchema = z.object({ + technique_id: z.string(), + api: z.string(), + tag: z.string(), +}) + +export const ImportStatisticsSchema = z.object({ + total_imports: z.number(), + total_exports: z.number(), + suspicious_count: z.number(), + library_count: z.number(), +}) + +export const ImportResultSchema = z.object({ + imports: z.array(ImportEntrySchema), + exports: z.array(ExportEntrySchema), + libraries: z.array(z.string()), + suspicious_combinations: z.array(SuspiciousCombinationSchema), + mitre_mappings: z.array(ImportMitreMappingSchema), + statistics: ImportStatisticsSchema, +}) + +export const ExtractedStringSchema = z.object({ + value: z.string(), + offset: z.number(), + encoding: StringEncodingSchema, + length: z.number(), + category: StringCategorySchema, + is_suspicious: z.boolean(), + section: z.string().nullable(), +}) + +export const StringStatisticsSchema = z.object({ + total: z.number(), + by_encoding: z.record(z.string(), z.number()), + by_category: z.record(z.string(), z.number()), + suspicious_count: z.number(), +}) + +export const StringResultSchema = z.object({ + strings: z.array(ExtractedStringSchema), + statistics: StringStatisticsSchema, +}) + +export const SectionEntropySchema = z.object({ + name: z.string(), + entropy: z.number(), + size: z.number(), + classification: EntropyClassificationSchema, + virtual_to_raw_ratio: z.number(), + is_anomalous: z.boolean(), + flags: z.array(EntropyFlagSchema), +}) + +export const PackingIndicatorSchema = z.object({ + indicator_type: z.string(), + description: z.string(), + evidence: z.string(), + packer_name: z.string().nullable(), +}) + +export const EntropyResultSchema = z.object({ + overall_entropy: z.number(), + sections: z.array(SectionEntropySchema), + packing_detected: z.boolean(), + packer_name: z.string().nullable(), + packing_indicators: z.array(PackingIndicatorSchema), +}) + +export const InstructionInfoSchema = z.object({ + address: z.number(), + bytes: z.array(z.number()), + mnemonic: z.string(), + operands: z.string(), + size: z.number(), + flow_control: FlowControlTypeSchema, +}) + +export const BasicBlockInfoSchema = z.object({ + start_address: z.number(), + end_address: z.number(), + instruction_count: z.number(), + instructions: z.array(InstructionInfoSchema), + successors: z.array(z.number()), + predecessors: z.array(z.number()), +}) + +export const CfgNodeSchema = z.object({ + id: z.number(), + label: z.string(), + instruction_count: z.number(), + instructions_preview: z.string(), +}) + +export const CfgEdgeSchema = z.object({ + from: z.number(), + to: z.number(), + edge_type: CfgEdgeTypeSchema, +}) + +export const FunctionCfgSchema = z.object({ + nodes: z.array(CfgNodeSchema), + edges: z.array(CfgEdgeSchema), +}) + +export const FunctionInfoSchema = z.object({ + address: z.number(), + name: z.string().nullable(), + size: z.number(), + instruction_count: z.number(), + basic_blocks: z.array(BasicBlockInfoSchema), + is_entry_point: z.boolean(), + cfg: FunctionCfgSchema, +}) + +export const DisassemblyResultSchema = z.object({ + functions: z.array(FunctionInfoSchema), + total_instructions: z.number(), + total_functions: z.number(), + architecture_bits: z.number(), + entry_function_address: z.number(), +}) + +export const ScoringDetailSchema = z.object({ + rule: z.string(), + points: z.number(), + evidence: z.string(), +}) + +export const ScoringCategorySchema = z.object({ + name: z.string(), + score: z.number(), + max_score: z.number(), + details: z.array(ScoringDetailSchema), +}) + +export const ThreatMitreMappingSchema = z.object({ + technique_id: z.string(), + technique_name: z.string(), + tactic: z.string(), + evidence: z.string(), +}) + +export const YaraMetadataSchema = z.object({ + description: z.string().nullable(), + category: z.string().nullable(), + severity: z.string().nullable(), +}) + +export const YaraStringMatchSchema = z.object({ + identifier: z.string(), + match_count: z.number(), +}) + +export const YaraMatchSchema = z.object({ + rule_name: z.string(), + tags: z.array(z.string()), + metadata: YaraMetadataSchema, + matched_strings: z.array(YaraStringMatchSchema), +}) + +export const ThreatResultSchema = z.object({ + total_score: z.number(), + risk_level: RiskLevelSchema, + categories: z.array(ScoringCategorySchema), + mitre_techniques: z.array(ThreatMitreMappingSchema), + yara_matches: z.array(YaraMatchSchema), + summary: z.string(), +}) + +export const AnalysisPassesSchema = z.object({ + format: FormatResultSchema.optional(), + imports: ImportResultSchema.optional(), + strings: StringResultSchema.optional(), + entropy: EntropyResultSchema.optional(), + disassembly: DisassemblyResultSchema.optional(), + threat: ThreatResultSchema.optional(), +}) + +export const AnalysisResponseSchema = z.object({ + id: z.string(), + sha256: z.string(), + file_name: z.string(), + file_size: z.number(), + format: z.string(), + architecture: z.string(), + entry_point: z.number().nullable(), + threat_score: z.number().nullable(), + risk_level: z.string().nullable(), + slug: z.string(), + created_at: z.string(), + passes: AnalysisPassesSchema, +}) + +export const UploadResponseSchema = z.object({ + slug: z.string(), + cached: z.boolean(), +}) + +export const ApiErrorBodySchema = z.object({ + error: z.object({ + code: z.string(), + message: z.string(), + }), +}) diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/types/index.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/types/index.ts new file mode 100644 index 0000000..37c7bd7 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/api/types/index.ts @@ -0,0 +1,116 @@ +// =================== +// © AngelaMos | 2026 +// index.ts +// =================== + +import type { z } from 'zod' +import type { + AnalysisPassesSchema, + AnalysisResponseSchema, + ApiErrorBodySchema, + ArchitectureSchema, + BasicBlockInfoSchema, + BinaryFormatSchema, + CfgEdgeSchema, + CfgEdgeTypeSchema, + CfgNodeSchema, + DisassemblyResultSchema, + ElfInfoSchema, + EndiannessSchema, + EntropyClassificationSchema, + EntropyFlagSchema, + EntropyResultSchema, + ExportEntrySchema, + ExtractedStringSchema, + FlowControlTypeSchema, + FormatAnomalySchema, + FormatResultSchema, + FunctionCfgSchema, + FunctionInfoSchema, + ImportEntrySchema, + ImportMitreMappingSchema, + ImportResultSchema, + ImportStatisticsSchema, + InstructionInfoSchema, + MachOInfoSchema, + PackingIndicatorSchema, + PeDllCharacteristicsSchema, + PeInfoSchema, + RiskLevelSchema, + ScoringCategorySchema, + ScoringDetailSchema, + SectionEntropySchema, + SectionInfoSchema, + SectionPermissionsSchema, + SegmentInfoSchema, + SeveritySchema, + StringCategorySchema, + StringEncodingSchema, + StringResultSchema, + StringStatisticsSchema, + SuspiciousCombinationSchema, + ThreatMitreMappingSchema, + ThreatResultSchema, + UploadResponseSchema, + YaraMatchSchema, + YaraMetadataSchema, + YaraStringMatchSchema, +} from '../schemas' + +export type BinaryFormat = z.infer +export type Architecture = z.infer +export type Endianness = z.infer +export type RiskLevel = z.infer +export type Severity = z.infer +export type StringEncoding = z.infer +export type StringCategory = z.infer +export type EntropyClassification = z.infer +export type EntropyFlag = z.infer +export type FlowControlType = z.infer +export type CfgEdgeType = z.infer + +export type SectionPermissions = z.infer +export type SectionInfo = z.infer +export type SegmentInfo = z.infer +export type FormatAnomaly = z.infer +export type PeDllCharacteristics = z.infer +export type PeInfo = z.infer +export type ElfInfo = z.infer +export type MachOInfo = z.infer +export type FormatResult = z.infer + +export type ImportEntry = z.infer +export type ExportEntry = z.infer +export type SuspiciousCombination = z.infer +export type ImportMitreMapping = z.infer +export type ImportStatistics = z.infer +export type ImportResult = z.infer + +export type ExtractedString = z.infer +export type StringStatistics = z.infer +export type StringResult = z.infer + +export type SectionEntropy = z.infer +export type PackingIndicator = z.infer +export type EntropyResult = z.infer + +export type InstructionInfo = z.infer +export type BasicBlockInfo = z.infer +export type CfgNode = z.infer +export type CfgEdge = z.infer +export type FunctionCfg = z.infer +export type FunctionInfo = z.infer +export type DisassemblyResult = z.infer + +export type ScoringDetail = z.infer +export type ScoringCategory = z.infer +export type ThreatMitreMapping = z.infer +export type YaraMetadata = z.infer +export type YaraStringMatch = z.infer +export type YaraMatch = z.infer +export type ThreatResult = z.infer + +export type AnalysisPasses = z.infer +export type AnalysisResponse = z.infer +export type UploadResponse = z.infer +export type ApiErrorBody = z.infer diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/config.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/config.ts new file mode 100644 index 0000000..58faf70 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/config.ts @@ -0,0 +1,76 @@ +// =================== +// © AngelaMos | 2026 +// config.ts +// =================== + +export const API_ENDPOINTS = { + UPLOAD: '/upload', + ANALYSIS: (slug: string) => `/analysis/${slug}`, + HEALTH: '/health', +} as const + +export const QUERY_KEYS = { + ANALYSIS: { + BY_SLUG: (slug: string) => ['analysis', slug] as const, + }, +} as const + +export const ROUTES = { + HOME: '/', + ANALYSIS: '/analysis/:slug', +} as const + +export const STORAGE_KEYS = { + UI: 'ui-storage', +} as const + +export const QUERY_CONFIG = { + STALE_TIME: { + USER: 1000 * 60 * 5, + STATIC: Infinity, + FREQUENT: 1000 * 30, + }, + GC_TIME: { + DEFAULT: 1000 * 60 * 30, + LONG: 1000 * 60 * 60, + }, + RETRY: { + DEFAULT: 3, + NONE: 0, + }, +} as const + +export const HTTP_STATUS = { + OK: 200, + CREATED: 201, + NO_CONTENT: 204, + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + CONFLICT: 409, + TOO_MANY_REQUESTS: 429, + INTERNAL_SERVER: 500, +} as const + +export const UPLOAD_TIMEOUT_MS = 120_000 + +export const RISK_LEVEL_COLORS: Record = { + Benign: '#22c55e', + Low: '#84cc16', + Medium: '#eab308', + High: '#f97316', + Critical: '#ef4444', +} as const + +export const ENTROPY_CLASSIFICATION_COLORS: Record = { + Plaintext: '#22c55e', + NativeCode: '#3b82f6', + Compressed: '#eab308', + Packed: '#f97316', + Encrypted: '#ef4444', +} as const + +export type ApiEndpoint = typeof API_ENDPOINTS +export type QueryKey = typeof QUERY_KEYS +export type Route = typeof ROUTES diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/api.config.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/api.config.ts new file mode 100644 index 0000000..f48a4d0 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/api.config.ts @@ -0,0 +1,17 @@ +// =================== +// © AngelaMos | 2026 +// api.config.ts +// =================== + +import axios, { type AxiosInstance } from 'axios' + +const getBaseURL = (): string => { + return import.meta.env.VITE_API_URL ?? '/api' +} + +export const apiClient: AxiosInstance = axios.create({ + baseURL: getBaseURL(), + timeout: 15000, + headers: { 'Content-Type': 'application/json' }, + withCredentials: true, +}) diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/errors.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/errors.ts new file mode 100644 index 0000000..492672f --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/errors.ts @@ -0,0 +1,114 @@ +/** + * ©AngelaMos | 2026 + * errors.ts + */ + +import type { AxiosError } from 'axios' + +export const ApiErrorCode = { + NETWORK_ERROR: 'NETWORK_ERROR', + VALIDATION_ERROR: 'VALIDATION_ERROR', + AUTHENTICATION_ERROR: 'AUTHENTICATION_ERROR', + AUTHORIZATION_ERROR: 'AUTHORIZATION_ERROR', + NOT_FOUND: 'NOT_FOUND', + CONFLICT: 'CONFLICT', + RATE_LIMITED: 'RATE_LIMITED', + SERVER_ERROR: 'SERVER_ERROR', + UNKNOWN_ERROR: 'UNKNOWN_ERROR', +} as const + +export type ApiErrorCode = (typeof ApiErrorCode)[keyof typeof ApiErrorCode] + +export class ApiError extends Error { + readonly code: ApiErrorCode + readonly statusCode: number + readonly details?: Record + + constructor( + message: string, + code: ApiErrorCode, + statusCode: number, + details?: Record + ) { + super(message) + this.name = 'ApiError' + this.code = code + this.statusCode = statusCode + this.details = details + } + + getUserMessage(): string { + const messages: Record = { + [ApiErrorCode.NETWORK_ERROR]: + 'Unable to connect. Please check your internet connection.', + [ApiErrorCode.VALIDATION_ERROR]: 'Please check your input and try again.', + [ApiErrorCode.AUTHENTICATION_ERROR]: + 'Your session has expired. Please log in again.', + [ApiErrorCode.AUTHORIZATION_ERROR]: + 'You do not have permission to perform this action.', + [ApiErrorCode.NOT_FOUND]: 'The requested resource was not found.', + [ApiErrorCode.CONFLICT]: + 'This operation conflicts with an existing resource.', + [ApiErrorCode.RATE_LIMITED]: + 'Too many requests. Please wait a moment and try again.', + [ApiErrorCode.SERVER_ERROR]: + 'Something went wrong on our end. Please try again later.', + [ApiErrorCode.UNKNOWN_ERROR]: + 'An unexpected error occurred. Please try again.', + } + return messages[this.code] + } +} + +interface ApiErrorResponse { + detail?: string | { msg: string; type: string }[] + message?: string +} + +export function transformAxiosError(error: AxiosError): ApiError { + if (!error.response) { + return new ApiError('Network error', ApiErrorCode.NETWORK_ERROR, 0) + } + + const { status } = error.response + const data = error.response.data as ApiErrorResponse | undefined + let message = 'An error occurred' + let details: Record | undefined + + if (data?.detail) { + if (typeof data.detail === 'string') { + message = data.detail + } else if (Array.isArray(data.detail)) { + details = { validation: [] } + data.detail.forEach((err) => { + details?.validation.push(err.msg) + }) + message = 'Validation error' + } + } else if (data?.message) { + message = data.message + } + + const codeMap: Record = { + 400: ApiErrorCode.VALIDATION_ERROR, + 401: ApiErrorCode.AUTHENTICATION_ERROR, + 403: ApiErrorCode.AUTHORIZATION_ERROR, + 404: ApiErrorCode.NOT_FOUND, + 409: ApiErrorCode.CONFLICT, + 429: ApiErrorCode.RATE_LIMITED, + 500: ApiErrorCode.SERVER_ERROR, + 502: ApiErrorCode.SERVER_ERROR, + 503: ApiErrorCode.SERVER_ERROR, + 504: ApiErrorCode.SERVER_ERROR, + } + + const code = codeMap[status] || ApiErrorCode.UNKNOWN_ERROR + + return new ApiError(message, code, status, details) +} + +declare module '@tanstack/react-query' { + interface Register { + defaultError: ApiError + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/index.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/index.ts new file mode 100644 index 0000000..f6dc363 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/index.ts @@ -0,0 +1,8 @@ +// =================== +// © AngelaMos | 2026 +// index.ts +// =================== + +export * from './api.config' +export * from './errors' +export * from './query.config' diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/query.config.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/query.config.ts new file mode 100644 index 0000000..42084ec --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/api/query.config.ts @@ -0,0 +1,105 @@ +// =================== +// © AngelaMos | 2026 +// query.config.ts +// =================== + +import { MutationCache, QueryCache, QueryClient } from '@tanstack/react-query' +import { toast } from 'sonner' +import { QUERY_CONFIG } from '@/config' +import { ApiError, ApiErrorCode } from './errors' + +const NO_RETRY_ERROR_CODES: readonly ApiErrorCode[] = [ + ApiErrorCode.AUTHENTICATION_ERROR, + ApiErrorCode.AUTHORIZATION_ERROR, + ApiErrorCode.NOT_FOUND, + ApiErrorCode.VALIDATION_ERROR, +] as const + +const shouldRetryQuery = (failureCount: number, error: Error): boolean => { + if (error instanceof ApiError) { + if (NO_RETRY_ERROR_CODES.includes(error.code)) { + return false + } + } + return failureCount < QUERY_CONFIG.RETRY.DEFAULT +} + +const calculateRetryDelay = (attemptIndex: number): number => { + const baseDelay = 1000 + const maxDelay = 30000 + return Math.min(baseDelay * 2 ** attemptIndex, maxDelay) +} + +const handleQueryCacheError = ( + error: Error, + query: { state: { data: unknown } } +): void => { + if (query.state.data !== undefined) { + const message = + error instanceof ApiError + ? error.getUserMessage() + : 'Background update failed' + toast.error(message) + } +} + +const handleMutationCacheError = ( + error: Error, + _variables: unknown, + _context: unknown, + mutation: { options: { onError?: unknown } } +): void => { + if (mutation.options.onError === undefined) { + const message = + error instanceof ApiError ? error.getUserMessage() : 'Operation failed' + toast.error(message) + } +} + +export const QUERY_STRATEGIES = { + standard: { + staleTime: QUERY_CONFIG.STALE_TIME.USER, + gcTime: QUERY_CONFIG.GC_TIME.DEFAULT, + }, + frequent: { + staleTime: QUERY_CONFIG.STALE_TIME.FREQUENT, + gcTime: QUERY_CONFIG.GC_TIME.DEFAULT, + refetchInterval: QUERY_CONFIG.STALE_TIME.FREQUENT, + }, + static: { + staleTime: QUERY_CONFIG.STALE_TIME.STATIC, + gcTime: QUERY_CONFIG.GC_TIME.LONG, + refetchOnMount: false, + refetchOnWindowFocus: false, + }, + auth: { + staleTime: QUERY_CONFIG.STALE_TIME.USER, + gcTime: QUERY_CONFIG.GC_TIME.DEFAULT, + retry: QUERY_CONFIG.RETRY.NONE, + }, +} as const + +export type QueryStrategy = keyof typeof QUERY_STRATEGIES + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: QUERY_CONFIG.STALE_TIME.USER, + gcTime: QUERY_CONFIG.GC_TIME.DEFAULT, + retry: shouldRetryQuery, + retryDelay: calculateRetryDelay, + refetchOnWindowFocus: true, + refetchOnMount: true, + refetchOnReconnect: true, + }, + mutations: { + retry: QUERY_CONFIG.RETRY.NONE, + }, + }, + queryCache: new QueryCache({ + onError: handleQueryCacheError, + }), + mutationCache: new MutationCache({ + onError: handleMutationCacheError, + }), +}) diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/routers.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/routers.tsx new file mode 100644 index 0000000..8958396 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/routers.tsx @@ -0,0 +1,30 @@ +// =================== +// © AngelaMos | 2026 +// routers.tsx +// =================== + +import { createBrowserRouter, type RouteObject } from 'react-router-dom' +import { ROUTES } from '@/config' +import { Shell } from './shell' + +const routes: RouteObject[] = [ + { + element: , + children: [ + { + path: ROUTES.HOME, + lazy: () => import('@/pages/landing'), + }, + { + path: ROUTES.ANALYSIS, + lazy: () => import('@/pages/analysis'), + }, + { + path: '*', + lazy: () => import('@/pages/landing'), + }, + ], + }, +] + +export const router = createBrowserRouter(routes) diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.module.scss b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.module.scss new file mode 100644 index 0000000..53789f3 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.module.scss @@ -0,0 +1,331 @@ +// =================== +// © AngelaMos | 2026 +// shell.module.scss +// =================== + +@use '@/styles' as *; + +$sidebar-width: 240px; +$sidebar-collapsed-width: 64px; +$header-height: 56px; + +.shell { + display: flex; + min-height: 100vh; + min-height: 100dvh; +} + +.sidebar { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: $sidebar-width; + background: $bg-surface-100; + border-right: 1px solid $border-default; + display: flex; + flex-direction: column; + z-index: $z-fixed; + @include transition-fast; + + &.collapsed { + width: $sidebar-collapsed-width; + } + + @include breakpoint-down('sm') { + transform: translateX(-100%); + + &.open { + transform: translateX(0); + } + + &.collapsed { + width: $sidebar-width; + } + } +} + +.sidebarHeader { + height: $header-height; + padding: 0 $space-3; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid $border-default; + + .sidebar.collapsed & { + justify-content: center; + padding: 0; + } +} + +.logo { + font-size: $font-size-base; + font-weight: $font-weight-semibold; + color: $text-default; + @include transition-fast; + + .sidebar.collapsed & { + display: none; + } +} + +.nav { + flex: 1; + padding: $space-3; + display: flex; + flex-direction: column; + gap: $space-1; +} + +.navItem { + display: flex; + align-items: center; + gap: $space-3; + padding: $space-2 $space-3; + border-radius: $radius-md; + font-size: $font-size-sm; + color: $text-light; + @include transition-fast; + + @include hover { + background: $bg-surface-200; + color: $text-default; + } + + &.active { + background: $bg-selection; + color: $text-default; + } + + .sidebar.collapsed & { + justify-content: center; + } +} + +.navIcon { + width: 17px; + height: 17px; + flex-shrink: 0; +} + +.navLabel { + @include transition-fast; + + .sidebar.collapsed & { + display: none; + } +} + +.adminItem { + margin-top: auto; + border-top: 1px solid $border-default; + padding-top: $space-3; +} + +.collapseBtn { + width: 45px; + height: 45px; + border-radius: $radius-md; + color: $text-light; + @include flex-center; + @include transition-fast; + + svg { + width: 23.5px; + height: 23.5px; + } + + @include hover { + background: $bg-surface-200; + color: $text-default; + } + + @include breakpoint-down('sm') { + display: none; + } +} + +.sidebarFooter { + padding: $space-3; + border-top: 1px solid $border-default; +} + +.logoutBtn { + width: 100%; + display: flex; + align-items: center; + gap: $space-3; + padding: $space-3; + border-radius: $radius-md; + font-size: $font-size-sm; + color: $text-default; + @include transition-fast; + + @include hover { + background: $bg-surface-200; + } + + .sidebar.collapsed & { + justify-content: center; + + .logoutText { + display: none; + } + } +} + +.logoutIcon { + width: 18px; + height: 18px; + flex-shrink: 0; +} + +.logoutText { + font-weight: $font-weight-medium; + @include transition-fast; +} + +.overlay { + position: fixed; + inset: 0; + background: rgb(0, 0, 0, 50%); + z-index: calc($z-fixed - 1); + display: none; + border: none; + padding: 0; + cursor: pointer; + + @include breakpoint-down('sm') { + display: block; + } +} + +.main { + flex: 1; + display: flex; + flex-direction: column; + margin-left: $sidebar-width; + min-width: 0; + @include transition-fast; + + &.collapsed { + margin-left: $sidebar-collapsed-width; + } + + @include breakpoint-down('sm') { + margin-left: 0; + + &.collapsed { + margin-left: 0; + } + } +} + +.header { + position: sticky; + top: 0; + height: $header-height; + background: $bg-surface-100; + border-bottom: 1px solid $border-default; + z-index: $z-sticky; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 $space-4; +} + +.headerLeft { + display: flex; + align-items: center; + gap: $space-3; +} + +.menuBtn { + display: none; + width: 36px; + height: 36px; + border-radius: $radius-md; + color: $text-light; + align-items: center; + justify-content: center; + @include transition-fast; + + svg { + width: 20px; + height: 20px; + } + + @include hover { + background: $bg-surface-200; + color: $text-default; + } + + @media (width <= 479px) { + display: flex; + } +} + +.pageTitle { + font-size: $font-size-base; + font-weight: $font-weight-medium; + color: $text-default; + margin-left: 7px; +} + +.headerRight { + display: flex; + align-items: center; + gap: $space-3; +} + +.avatar { + width: 32px; + height: 32px; + border-radius: $radius-full; + background: $bg-surface-300; + color: $text-light; + font-size: $font-size-sm; + font-weight: $font-weight-medium; + @include flex-center; + cursor: pointer; + @include transition-fast; + + @include hover { + filter: brightness(1.2); + } +} + +.content { + flex: 1; + overflow-y: auto; +} + +.loading { + @include flex-center; + height: 100%; + color: $text-muted; +} + +.error { + @include flex-column-center; + height: 100%; + gap: $space-4; + padding: $space-6; + color: $error-default; + + h2 { + font-size: $font-size-xl; + font-weight: $font-weight-semibold; + } + + pre { + font-family: $font-mono; + font-size: $font-size-sm; + padding: $space-4; + background: $bg-surface-200; + border-radius: $radius-lg; + overflow-x: auto; + max-width: 100%; + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.tsx new file mode 100644 index 0000000..6bfd851 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/shell.tsx @@ -0,0 +1,36 @@ +// =================== +// © AngelaMos | 2026 +// shell.tsx +// =================== + +import { Suspense } from 'react' +import { ErrorBoundary } from 'react-error-boundary' +import { Outlet } from 'react-router-dom' +import styles from './shell.module.scss' + +function ShellErrorFallback({ error }: { error: Error }): React.ReactElement { + return ( +
+

Something went wrong

+
{error.message}
+
+ ) +} + +function ShellLoading(): React.ReactElement { + return
Loading...
+} + +export function Shell(): React.ReactElement { + return ( +
+
+ + }> + + + +
+
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/toast.module.scss b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/toast.module.scss new file mode 100644 index 0000000..29b3f61 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/app/toast.module.scss @@ -0,0 +1,67 @@ +// =================== +// © AngelaMos | 2026 +// toast.module.scss +// =================== + +@use '@/styles' as *; + +:global { + [data-sonner-toaster] { + --normal-bg: #{$bg-surface-100}; + --normal-border: #{$border-default}; + --normal-text: #{$text-default}; + + --success-bg: #{$bg-surface-100}; + --success-border: #{$border-default}; + --success-text: #{$text-default}; + + --error-bg: #{$bg-surface-100}; + --error-border: #{$error-default}; + --error-text: #{$text-default}; + + --warning-bg: #{$bg-surface-100}; + --warning-border: #{$border-default}; + --warning-text: #{$text-default}; + + --info-bg: #{$bg-surface-100}; + --info-border: #{$border-default}; + --info-text: #{$text-default}; + + font-family: $font-sans; + } + + [data-sonner-toast] { + border-radius: $radius-md; + padding: $space-3 $space-4; + font-size: $font-size-sm; + border: 1px solid $border-default; + background: $bg-surface-100; + color: $text-default; + + [data-title] { + font-weight: $font-weight-medium; + } + + [data-description] { + color: $text-light; + font-size: $font-size-xs; + } + + [data-close-button] { + background: none; + border: none; + padding: 0; + cursor: pointer; + color: $text-muted; + @include transition-fast; + + @include hover { + color: $text-default; + } + } + } + + [data-sonner-toast][data-type='error'] { + border-color: $error-default; + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/format.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/format.ts new file mode 100644 index 0000000..3eb999d --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/format.ts @@ -0,0 +1,41 @@ +// =================== +// © AngelaMos | 2026 +// format.ts +// =================== + +const BYTE_UNITS = ['B', 'KB', 'MB', 'GB'] as const +const BYTES_PER_UNIT = 1024 +const DEFAULT_HEX_PAD = 8 +const DEFAULT_HASH_DISPLAY_LENGTH = 16 + +export function formatBytes(bytes: number): string { + let unitIndex = 0 + let value = bytes + while (value >= BYTES_PER_UNIT && unitIndex < BYTE_UNITS.length - 1) { + value /= BYTES_PER_UNIT + unitIndex++ + } + const decimals = unitIndex === 0 ? 0 : 2 + return `${value.toFixed(decimals)} ${BYTE_UNITS[unitIndex]}` +} + +export function formatHex(value: number, pad: number = DEFAULT_HEX_PAD): string { + return `0x${value.toString(16).toUpperCase().padStart(pad, '0')}` +} + +export function truncateHash( + hash: string, + length: number = DEFAULT_HASH_DISPLAY_LENGTH +): string { + if (hash.length <= length) return hash + return `${hash.slice(0, length)}\u2026` +} + +export async function copyToClipboard(text: string): Promise { + try { + await navigator.clipboard.writeText(text) + return true + } catch { + return false + } +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/index.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/index.ts new file mode 100644 index 0000000..58f981a --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/index.ts @@ -0,0 +1,7 @@ +// =================== +// © AngelaMos | 2026 +// index.ts +// =================== + +export * from './format' +export * from './shell.ui.store' diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/shell.ui.store.ts b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/shell.ui.store.ts new file mode 100644 index 0000000..e300578 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/core/lib/shell.ui.store.ts @@ -0,0 +1,63 @@ +/** + * ©AngelaMos | 2026 + * shell.ui.store.ts + */ + +import { create } from 'zustand' +import { devtools, persist } from 'zustand/middleware' + +type Theme = 'light' | 'dark' | 'system' + +interface UIState { + theme: Theme + sidebarOpen: boolean + sidebarCollapsed: boolean + setTheme: (theme: Theme) => void + toggleSidebar: () => void + setSidebarOpen: (open: boolean) => void + toggleSidebarCollapsed: () => void +} + +export const useUIStore = create()( + devtools( + persist( + (set) => ({ + theme: 'dark', + sidebarOpen: false, + sidebarCollapsed: false, + + setTheme: (theme) => set({ theme }, false, 'ui/setTheme'), + + toggleSidebar: () => + set( + (state) => ({ sidebarOpen: !state.sidebarOpen }), + false, + 'ui/toggleSidebar' + ), + + setSidebarOpen: (open) => + set({ sidebarOpen: open }, false, 'ui/setSidebarOpen'), + + toggleSidebarCollapsed: () => + set( + (state) => ({ sidebarCollapsed: !state.sidebarCollapsed }), + false, + 'ui/toggleSidebarCollapsed' + ), + }), + { + name: 'ui-storage', + partialize: (state) => ({ + theme: state.theme, + sidebarCollapsed: state.sidebarCollapsed, + }), + } + ), + { name: 'UIStore' } + ) +) + +export const useTheme = (): Theme => useUIStore((s) => s.theme) +export const useSidebarOpen = (): boolean => useUIStore((s) => s.sidebarOpen) +export const useSidebarCollapsed = (): boolean => + useUIStore((s) => s.sidebarCollapsed) diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/main.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/main.tsx new file mode 100644 index 0000000..ac31951 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/main.tsx @@ -0,0 +1,15 @@ +// =========================== +// ©AngelaMos | 2026 +// main.tsx +// =========================== + +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' +import './styles.scss' + +createRoot(document.getElementById('root')!).render( + + + +) diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/analysis.module.scss b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/analysis.module.scss new file mode 100644 index 0000000..929be48 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/analysis.module.scss @@ -0,0 +1,1129 @@ +// =================== +// © AngelaMos | 2026 +// analysis.module.scss +// =================== + +@use '@/styles' as *; + +$content-max: 1200px; +$sidebar-width: 240px; + +.page { + min-height: 100vh; + min-height: 100dvh; + background: $bg-default; + display: flex; + flex-direction: column; + align-items: center; + padding: $space-4 $space-4 $space-8; + + @include breakpoint-up('md') { + padding: $space-6 $space-8 $space-12; + } +} + +.state { + min-height: 100vh; + min-height: 100dvh; + background: $bg-default; + @include flex-column-center; + gap: $space-3; +} + +.stateCode { + font-size: $font-size-5xl; + font-weight: $font-weight-black; + color: $text-muted; + letter-spacing: $tracking-tight; +} + +.stateLabel { + font-family: $font-mono; + font-size: $font-size-xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-lighter; +} + +.stateBack { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; + padding: $space-2 $space-4; + border: 1px solid $border-default; + margin-top: $space-4; + @include transition-fast; + + @include hover { + border-color: $accent; + color: $accent; + } +} + +.backLink { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; + @include transition-fast; + + @include hover { + color: $accent; + } +} + +.header { + width: 100%; + max-width: $content-max; + padding-bottom: $space-4; + border-bottom: 1px solid $border-default; + display: flex; + flex-direction: column; + gap: $space-3; +} + +.headerTop { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; + gap: $space-3; +} + +.fileName { + font-size: $font-size-2xl; + font-weight: $font-weight-bold; + color: $text-default; + word-break: break-all; + + @include breakpoint-up('md') { + font-size: $font-size-3xl; + } +} + +.badges { + display: flex; + gap: $space-2; + flex-shrink: 0; +} + +.badge { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wider; + color: $text-lighter; + padding: $space-0-5 $space-2; + border: 1px solid $border-default; +} + +.headerMeta { + display: flex; + align-items: center; + gap: $space-5; + flex-wrap: wrap; +} + +.hashBtn { + display: flex; + align-items: center; + gap: $space-2; + @include transition-fast; + + @include hover { + .hashValue { + color: $text-default; + } + } +} + +.hashLabel { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; +} + +.hashValue { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-lighter; + @include transition-fast; +} + +.metaItem { + display: flex; + align-items: center; + gap: $space-2; +} + +.metaLabel { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; +} + +.metaValue { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-lighter; +} + +.scoreCard { + width: 100%; + max-width: $content-max; + padding: $space-6 0; + border-bottom: 1px solid $border-default; + display: flex; + flex-direction: column; + gap: $space-5; + + @include breakpoint-up('lg') { + flex-direction: row; + align-items: flex-start; + } +} + +.scoreMain { + display: flex; + align-items: baseline; + gap: $space-4; + flex-shrink: 0; +} + +.scoreNumber { + font-size: $font-size-5xl; + font-weight: $font-weight-black; + line-height: $line-height-none; + + @include breakpoint-up('md') { + font-size: $font-size-7xl; + } +} + +.scoreInfo { + display: flex; + flex-direction: column; + gap: $space-1; +} + +.riskLabel { + font-family: $font-mono; + font-size: $font-size-sm; + font-weight: $font-weight-bold; + text-transform: uppercase; + letter-spacing: $tracking-wider; +} + +.scoreSuffix { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; +} + +.scoreBars { + flex: 1; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: $space-2 $space-5; + + @include breakpoint-up('lg') { + grid-template-columns: repeat(4, 1fr); + } +} + +.scoreBar { + display: flex; + flex-direction: column; + gap: $space-1; +} + +.scoreBarHeader { + display: flex; + justify-content: space-between; + align-items: center; +} + +.scoreBarName { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wide; + color: $text-lighter; + @include truncate; +} + +.scoreBarValue { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; +} + +.scoreBarTrack { + height: 3px; + background: $border-muted; +} + +.scoreBarFill { + height: 100%; + background: $accent; + transition: width $duration-slow $ease-out; +} + +.tabBar { + width: 100%; + max-width: $content-max; + display: flex; + gap: $space-1; + border-bottom: 1px solid $border-default; + overflow-x: auto; + @include hide-scrollbar; +} + +.tab { + font-family: $font-mono; + font-size: $font-size-2xs; + text-transform: uppercase; + letter-spacing: $tracking-wider; + color: $text-muted; + padding: $space-3 $space-4; + border-bottom: 2px solid transparent; + white-space: nowrap; + @include transition-fast; + + @include hover { + color: $text-lighter; + } + + &.tabActive { + color: $text-default; + border-bottom-color: $accent; + } +} + +.tabContent { + width: 100%; + max-width: $content-max; +} + +.tabPanel { + padding: $space-5 0; + display: flex; + flex-direction: column; + gap: $space-5; +} + +.noData { + font-family: $font-mono; + font-size: $font-size-xs; + text-transform: uppercase; + letter-spacing: $tracking-wider; + color: $text-muted; + padding: $space-10 0; + text-align: center; +} + +.summaryGrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: $space-3; + + @include breakpoint-up('md') { + grid-template-columns: repeat(3, 1fr); + } +} + +.summaryCard { + padding: $space-4; + border: 1px solid $border-default; + display: flex; + flex-direction: column; + gap: $space-1; +} + +.summaryLabel { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; +} + +.summaryValue { + font-family: $font-mono; + font-size: $font-size-sm; + color: $text-default; +} + +.summaryDetail { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-lighter; +} + +.overviewSection { + display: flex; + flex-direction: column; + gap: $space-3; +} + +.sectionLabel { + font-family: $font-mono; + font-size: $font-size-2xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $accent; +} + +.anomalyList { + display: flex; + flex-direction: column; + gap: $space-1; +} + +.anomalyItem { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-lighter; + padding: $space-2 $space-3; + border-left: 2px solid $error-default; + background: $bg-surface-75; +} + +.mitrePills { + display: flex; + flex-wrap: wrap; + gap: $space-2; +} + +.mitrePill { + display: flex; + align-items: center; + gap: $space-2; + padding: $space-1 $space-2-5; + border: 1px solid $border-default; + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + @include transition-fast; + + @include hover { + border-color: $accent; + color: $accent; + } +} + +.mitreId { + letter-spacing: $tracking-wider; + color: $text-default; +} + +.mitreName { + color: $text-lighter; +} + +.metaGrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: $space-2 $space-4; + + @include breakpoint-up('md') { + grid-template-columns: repeat(4, 1fr); + } +} + +.metaField { + display: flex; + flex-direction: column; + gap: $space-0-5; +} + +.metaFieldLabel { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; +} + +.metaFieldValue { + font-family: $font-mono; + font-size: $font-size-sm; + color: $text-default; +} + +.libList { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: $space-2; + padding-top: $space-2; +} + +.libItem { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-lighter; + padding: $space-0-5 $space-2; + border: 1px solid $border-muted; +} + +.tableWrap { + overflow-x: auto; + @include hide-scrollbar; +} + +.dataTable { + width: 100%; + border-collapse: collapse; + font-family: $font-mono; + font-size: $font-size-xs; + + th { + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; + padding: $space-2 $space-3; + text-align: left; + border-bottom: 1px solid $border-default; + white-space: nowrap; + font-weight: $font-weight-regular; + } + + td { + padding: $space-1-5 $space-3; + color: $text-lighter; + border-bottom: 1px solid $border-muted; + white-space: nowrap; + } +} + +.tableRow { + @include transition-fast; + + @include hover { + background: $bg-surface-75; + } + + &.suspicious { + td { + color: $error-light; + } + } + + &.blockBoundary { + td { + border-top: 2px solid $border-strong; + } + } +} + +.cellMono { + font-family: $font-mono; +} + +.cellRight { + text-align: right; +} + +.cellMnemonic { + font-family: $font-mono; + font-weight: $font-weight-bold; + color: $text-default; +} + +.perms { + display: flex; + gap: $space-0-5; + font-family: $font-mono; + font-size: $font-size-3xs; + letter-spacing: $tracking-wide; +} + +.permActive { + color: $text-default; +} + +.permInactive { + color: $text-muted; +} + +.permExec { + color: $accent; +} + +.collapseBtn { + display: flex; + align-items: center; + gap: $space-2; + width: 100%; + text-align: left; + @include transition-fast; + + @include hover { + .collapseIcon { + color: $text-lighter; + } + } +} + +.collapseIcon { + font-size: $font-size-3xs; + color: $text-muted; + @include transition-fast; +} + +.libraryGroup { + border: 1px solid $border-muted; +} + +.libraryHeader { + width: 100%; + display: flex; + align-items: center; + gap: $space-3; + padding: $space-2-5 $space-3; + text-align: left; + @include transition-fast; + + @include hover { + background: $bg-surface-75; + } +} + +.libraryName { + font-family: $font-mono; + font-size: $font-size-sm; + color: $text-default; + font-weight: $font-weight-medium; +} + +.libraryMeta { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; + display: flex; + gap: $space-3; + margin-left: auto; +} + +.suspiciousCount { + color: $error-light; +} + +.tagRow { + display: flex; + flex-wrap: wrap; + gap: $space-1; +} + +.threatTag { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wide; + color: $error-light; + padding: $space-0-5 $space-1-5; + border: 1px solid $error-default; +} + +.alertCards { + display: flex; + flex-direction: column; + gap: $space-3; +} + +.alertCard { + padding: $space-3 $space-4; + border: 1px solid $error-default; + background: $bg-surface-75; + display: flex; + flex-direction: column; + gap: $space-2; +} + +.alertHeader { + display: flex; + align-items: center; + gap: $space-3; + flex-wrap: wrap; +} + +.alertName { + font-family: $font-mono; + font-size: $font-size-sm; + font-weight: $font-weight-bold; + color: $text-default; +} + +.alertDesc { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-lighter; +} + +.alertApis { + display: flex; + flex-wrap: wrap; + gap: $space-1; +} + +.apiTag { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-lighter; + padding: $space-0-5 $space-1-5; + border: 1px solid $border-default; +} + +.severityBadge { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wider; + padding: $space-0-5 $space-2; + border: 1px solid; +} + +.severityLow { + color: #84cc16; + border-color: #84cc16; +} + +.severityMedium { + color: #eab308; + border-color: #eab308; +} + +.severityHigh { + color: #f97316; + border-color: #f97316; +} + +.severityCritical { + color: #ef4444; + border-color: #ef4444; +} + +.categoryBadge { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wide; + color: $text-lighter; + padding: $space-0-5 $space-1-5; + border: 1px solid $border-default; +} + +.filterBar { + display: flex; + flex-wrap: wrap; + gap: $space-2; + align-items: center; +} + +.searchInput { + flex: 1; + min-width: 200px; + padding: $space-2 $space-3; + background: $bg-surface-75; + border: 1px solid $border-default; + font-family: $font-mono; + font-size: $font-size-xs; + text-transform: uppercase; + letter-spacing: $tracking-wide; + color: $text-default; + @include transition-fast; + + &::placeholder { + color: $text-muted; + } + + &:focus { + border-color: $accent; + outline: none; + } +} + +.filterSelect { + padding: $space-2 $space-3; + background: $bg-surface-75; + border: 1px solid $border-default; + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wide; + color: $text-lighter; + cursor: pointer; + @include transition-fast; + + &:focus { + border-color: $accent; + outline: none; + } +} + +.filterToggle { + padding: $space-2 $space-3; + border: 1px solid $border-default; + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wider; + color: $text-muted; + @include transition-fast; + + @include hover { + border-color: $border-strong; + color: $text-lighter; + } + + &.filterActive { + border-color: $error-default; + color: $error-light; + } +} + +.filterCount { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; + letter-spacing: $tracking-wide; +} + +.stringValue { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-lighter; + text-align: left; + word-break: break-all; + white-space: pre-wrap; +} + +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: $space-4; +} + +.pageBtn { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wider; + color: $text-lighter; + padding: $space-1-5 $space-3; + border: 1px solid $border-default; + @include transition-fast; + + @include hover { + border-color: $accent; + color: $accent; + } + + &:disabled { + opacity: 0.3; + cursor: not-allowed; + } +} + +.pageInfo { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; + letter-spacing: $tracking-wide; +} + +.entropyOverall { + display: flex; + align-items: baseline; + gap: $space-3; + padding: $space-4 0; +} + +.entropyOverallLabel { + font-family: $font-mono; + font-size: $font-size-2xs; + text-transform: uppercase; + letter-spacing: $tracking-widest; + color: $text-muted; +} + +.entropyOverallValue { + font-size: $font-size-3xl; + font-weight: $font-weight-bold; + color: $text-default; +} + +.entropyOverallScale { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-muted; +} + +.packingAlert { + padding: $space-4; + border: 1px solid $error-default; + background: $bg-surface-75; + display: flex; + flex-direction: column; + gap: $space-2; +} + +.packingTitle { + font-family: $font-mono; + font-size: $font-size-sm; + font-weight: $font-weight-bold; + text-transform: uppercase; + letter-spacing: $tracking-wider; + color: $error-light; +} + +.packingName { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-default; +} + +.packingIndicator { + display: flex; + gap: $space-3; + font-family: $font-mono; + font-size: $font-size-xs; +} + +.packingType { + color: $text-muted; + text-transform: uppercase; + letter-spacing: $tracking-wide; + flex-shrink: 0; +} + +.packingEvidence { + color: $text-lighter; +} + +.entropyBars { + display: flex; + flex-direction: column; + gap: $space-3; +} + +.entropyRow { + display: flex; + flex-direction: column; + gap: $space-1; + padding: $space-2 0; + border-bottom: 1px solid $border-muted; + + &.anomalous { + border-left: 2px solid $error-default; + padding-left: $space-3; + } +} + +.entropyMeta { + display: flex; + justify-content: space-between; + align-items: center; +} + +.entropySectionName { + font-family: $font-mono; + font-size: $font-size-sm; + color: $text-default; +} + +.entropyClassification { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wider; +} + +.entropyBarWrap { + display: flex; + align-items: center; + gap: $space-3; +} + +.entropyBarTrack { + flex: 1; + height: 6px; + background: $border-muted; +} + +.entropyBarFill { + height: 100%; + transition: width $duration-slow $ease-out; +} + +.entropyValue { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-lighter; + min-width: 36px; + text-align: right; +} + +.entropyDetails { + display: flex; + align-items: center; + gap: $space-3; + flex-wrap: wrap; +} + +.entropyDetail { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; + letter-spacing: $tracking-wide; +} + +.entropyFlags { + display: flex; + gap: $space-1; +} + +.entropyFlag { + font-family: $font-mono; + font-size: $font-size-3xs; + text-transform: uppercase; + letter-spacing: $tracking-wide; + color: $error-light; + padding: $space-0-5 $space-1-5; + border: 1px solid $error-default; +} + +.disasmLayout { + display: flex; + gap: $space-4; + min-height: 500px; + + @include breakpoint-down('md') { + flex-direction: column; + } +} + +.fnSidebar { + width: $sidebar-width; + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: $space-2; + border-right: 1px solid $border-default; + padding-right: $space-4; + + @include breakpoint-down('md') { + width: 100%; + border-right: none; + border-bottom: 1px solid $border-default; + padding-right: 0; + padding-bottom: $space-4; + max-height: 200px; + } +} + +.fnList { + overflow-y: auto; + display: flex; + flex-direction: column; + gap: $space-0-5; + @include hide-scrollbar; +} + +.fnItem { + display: flex; + flex-direction: column; + gap: $space-0-5; + padding: $space-1-5 $space-2; + text-align: left; + @include transition-fast; + + @include hover { + background: $bg-surface-75; + } + + &.fnActive { + background: $bg-surface-100; + border-left: 2px solid $accent; + padding-left: calc($space-2 - 2px); + } + + &.fnEntry { + .fnAddr { + color: $accent; + } + } +} + +.fnAddr { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; + letter-spacing: $tracking-wide; +} + +.fnName { + font-family: $font-mono; + font-size: $font-size-xs; + color: $text-default; + @include truncate; +} + +.fnMeta { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; +} + +.disasmMain { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: $space-4; +} + +.fnHeader { + display: flex; + flex-direction: column; + gap: $space-1; +} + +.fnHeaderName { + font-size: $font-size-lg; + font-weight: $font-weight-bold; + color: $text-default; + font-family: $font-mono; +} + +.fnHeaderMeta { + font-family: $font-mono; + font-size: $font-size-3xs; + color: $text-muted; + letter-spacing: $tracking-wide; +} + +.cfgContainer { + overflow-x: auto; + padding: $space-4; + border: 1px solid $border-muted; + background: $bg-surface-75; +} + +.cfgSvg { + max-width: 100%; + height: auto; + min-height: 200px; +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/index.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/index.tsx new file mode 100644 index 0000000..1448819 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/index.tsx @@ -0,0 +1,195 @@ +// =================== +// © AngelaMos | 2026 +// index.tsx +// =================== + +import { useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import { toast } from 'sonner' +import type { AnalysisResponse } from '@/api' +import { useAnalysis } from '@/api' +import { RISK_LEVEL_COLORS, ROUTES } from '@/config' +import { copyToClipboard, formatBytes, formatHex, truncateHash } from '@/core/lib' +import styles from './analysis.module.scss' +import { TabDisassembly } from './tab-disassembly' +import { TabEntropy } from './tab-entropy' +import { TabHeaders } from './tab-headers' +import { TabImports } from './tab-imports' +import { TabOverview } from './tab-overview' +import { TabStrings } from './tab-strings' + +type TabId = + | 'overview' + | 'headers' + | 'imports' + | 'strings' + | 'entropy' + | 'disassembly' + +const TABS: readonly { id: TabId; label: string }[] = [ + { id: 'overview', label: 'OVERVIEW' }, + { id: 'headers', label: 'HEADERS' }, + { id: 'imports', label: 'IMPORTS' }, + { id: 'strings', label: 'STRINGS' }, + { id: 'entropy', label: 'ENTROPY' }, + { id: 'disassembly', label: 'DISASM' }, +] as const + +function renderTab(tab: TabId, data: AnalysisResponse): React.ReactElement { + switch (tab) { + case 'overview': + return + case 'headers': + return + case 'imports': + return + case 'strings': + return + case 'entropy': + return + case 'disassembly': + return + } +} + +function ScoreBar({ + name, + score, + maxScore, +}: { + name: string + score: number + maxScore: number +}): React.ReactElement { + const pct = maxScore > 0 ? (score / maxScore) * 100 : 0 + return ( +
+
+ {name} + + {score}/{maxScore} + +
+
+
+
+
+ ) +} + +export function Component(): React.ReactElement { + const { slug = '' } = useParams<{ slug: string }>() + const { data, isLoading, isError } = useAnalysis(slug) + const [activeTab, setActiveTab] = useState('overview') + + if (isLoading) { + return ( +
+ ANALYZING SPECIMEN\u2026 +
+ ) + } + + if (isError || !data) { + return ( +
+ 404 + SPECIMEN NOT FOUND + + NEW ANALYSIS + +
+ ) + } + + const riskColor = data.risk_level + ? (RISK_LEVEL_COLORS[data.risk_level] ?? '#888') + : '#888' + + const handleCopyHash = async () => { + const ok = await copyToClipboard(data.sha256) + if (ok) toast.success('SHA-256 copied') + } + + return ( +
+
+ + AXUMORTEM + +
+

{data.file_name}

+
+ {data.format} + {data.architecture} + {formatBytes(data.file_size)} +
+
+
+ + {data.entry_point !== null && ( + + ENTRY + + {formatHex(data.entry_point)} + + + )} +
+
+ + {data.passes.threat && ( +
+
+ + {data.threat_score ?? 0} + +
+ + {data.risk_level ?? 'UNKNOWN'} + + / 100 THREAT SCORE +
+
+ {data.passes.threat.categories.length > 0 && ( +
+ {data.passes.threat.categories.map((cat) => ( + + ))} +
+ )} +
+ )} + + + +
{renderTab(activeTab, data)}
+
+ ) +} + +Component.displayName = 'Analysis' diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-disassembly.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-disassembly.tsx new file mode 100644 index 0000000..3f4eeba --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-disassembly.tsx @@ -0,0 +1,304 @@ +// =================== +// © AngelaMos | 2026 +// tab-disassembly.tsx +// =================== + +import { layout as dagreLayout, Graph } from '@dagrejs/dagre' +import { useMemo, useState } from 'react' +import type { + AnalysisResponse, + CfgEdge, + CfgEdgeType, + CfgNode, + FunctionInfo, +} from '@/api' +import { formatHex } from '@/core/lib' +import styles from './analysis.module.scss' + +const CFG_NODE_WIDTH = 160 +const CFG_NODE_HEIGHT = 40 +const CFG_RANK_SEP = 60 +const CFG_NODE_SEP = 30 + +const CFG_EDGE_COLORS: Record = { + Fallthrough: '#6b7280', + ConditionalTrue: '#22c55e', + ConditionalFalse: '#ef4444', + Unconditional: '#3b82f6', + Call: '#a855f7', +} + +interface LayoutNode { + id: number + x: number + y: number + label: string + instructionCount: number +} + +interface LayoutEdge { + from: { x: number; y: number } + to: { x: number; y: number } + color: string +} + +interface CfgLayout { + nodes: LayoutNode[] + edges: LayoutEdge[] + width: number + height: number +} + +function layoutCfg(nodes: CfgNode[], edges: CfgEdge[]): CfgLayout { + const g = new Graph() + g.setGraph({ + rankdir: 'TB', + ranksep: CFG_RANK_SEP, + nodesep: CFG_NODE_SEP, + }) + g.setDefaultEdgeLabel(() => ({})) + + for (const node of nodes) { + g.setNode(String(node.id), { + width: CFG_NODE_WIDTH, + height: CFG_NODE_HEIGHT, + }) + } + + for (const edge of edges) { + g.setEdge(String(edge.from), String(edge.to)) + } + + dagreLayout(g) + + const layoutNodes: LayoutNode[] = nodes.map((node) => { + const pos = g.node(String(node.id)) + return { + id: node.id, + x: pos.x, + y: pos.y, + label: node.label, + instructionCount: node.instruction_count, + } + }) + + const layoutEdges: LayoutEdge[] = edges.map((edge) => { + const fromPos = g.node(String(edge.from)) + const toPos = g.node(String(edge.to)) + return { + from: { x: fromPos.x, y: fromPos.y + CFG_NODE_HEIGHT / 2 }, + to: { x: toPos.x, y: toPos.y - CFG_NODE_HEIGHT / 2 }, + color: CFG_EDGE_COLORS[edge.edge_type], + } + }) + + const graphInfo = g.graph() + return { + nodes: layoutNodes, + edges: layoutEdges, + width: (graphInfo.width ?? 400) + CFG_NODE_WIDTH, + height: (graphInfo.height ?? 300) + CFG_NODE_HEIGHT, + } +} + +function CfgGraph({ + nodes, + edges, +}: { + nodes: CfgNode[] + edges: CfgEdge[] +}): React.ReactElement { + const layout = useMemo(() => layoutCfg(nodes, edges), [nodes, edges]) + const padX = CFG_NODE_WIDTH / 2 + const padY = CFG_NODE_HEIGHT / 2 + + return ( +
+ + Control flow graph + + + + + + + {layout.edges.map((edge, i) => ( + + ))} + + {layout.nodes.map((node) => ( + + + + {node.label} + + + {node.instructionCount} insn + + + ))} + +
+ ) +} + +function InstructionTable({ fn }: { fn: FunctionInfo }): React.ReactElement { + return ( +
+ + + + + + + + + + + {fn.basic_blocks.map((block, blockIdx) => ( + <> + {block.instructions.map((insn, i) => ( + 0 ? styles.blockBoundary : ''}`} + > + + + + + + ))} + + ))} + +
ADDRESSBYTESMNEMONICOPERANDS
{formatHex(insn.address)} + {insn.bytes + .map((b) => b.toString(16).padStart(2, '0')) + .join(' ')} + {insn.mnemonic}{insn.operands}
+
+ ) +} + +export function TabDisassembly({ + data, +}: { + data: AnalysisResponse +}): React.ReactElement { + const disasm = data.passes.disassembly + const [selectedAddr, setSelectedAddr] = useState(null) + + if (!disasm) { + return ( +
+ + Disassembly is only available for x86 and x86_64 binaries + +
+ ) + } + + const selectedFn = + disasm.functions.find((f) => f.address === selectedAddr) ?? + disasm.functions[0] ?? + null + + return ( +
+
+ + +
+ {selectedFn ? ( + <> +
+ + {selectedFn.name ?? `sub_${selectedFn.address.toString(16)}`} + + + {formatHex(selectedFn.address)} / {selectedFn.size} bytes /{' '} + {selectedFn.instruction_count} instructions /{' '} + {selectedFn.basic_blocks.length} blocks + +
+ + {selectedFn.cfg.nodes.length > 0 && ( +
+ CONTROL FLOW GRAPH + +
+ )} + + ) : ( + No functions found + )} +
+
+
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-entropy.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-entropy.tsx new file mode 100644 index 0000000..bad93d5 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-entropy.tsx @@ -0,0 +1,110 @@ +// =================== +// © AngelaMos | 2026 +// tab-entropy.tsx +// =================== + +import type { AnalysisResponse, SectionEntropy } from '@/api' +import { ENTROPY_CLASSIFICATION_COLORS } from '@/config' +import styles from './analysis.module.scss' + +const MAX_ENTROPY = 8 + +function EntropyBar({ + section, +}: { + section: SectionEntropy +}): React.ReactElement { + const pct = (section.entropy / MAX_ENTROPY) * 100 + const color = ENTROPY_CLASSIFICATION_COLORS[section.classification] ?? '#888' + + return ( +
+
+ {section.name} + + {section.classification} + +
+
+
+
+
+ {section.entropy.toFixed(2)} +
+
+ + SIZE {section.size.toLocaleString()} + + + V/R {section.virtual_to_raw_ratio.toFixed(2)} + + {section.flags.length > 0 && ( +
+ {section.flags.map((flag) => ( + + {flag} + + ))} +
+ )} +
+
+ ) +} + +export function TabEntropy({ + data, +}: { + data: AnalysisResponse +}): React.ReactElement { + const ent = data.passes.entropy + + if (!ent) { + return ( +
+ No entropy data available +
+ ) + } + + return ( +
+
+ OVERALL ENTROPY + + {ent.overall_entropy.toFixed(4)} + + / {MAX_ENTROPY} +
+ + {ent.packing_detected && ( +
+ PACKING DETECTED + {ent.packer_name && ( + {ent.packer_name} + )} + {ent.packing_indicators.map((ind, i) => ( +
+ {ind.indicator_type} + {ind.description} +
+ ))} +
+ )} + +
+ PER-SECTION ENTROPY +
+ {ent.sections.map((sec) => ( + + ))} +
+
+
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-headers.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-headers.tsx new file mode 100644 index 0000000..8409cbd --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-headers.tsx @@ -0,0 +1,315 @@ +// =================== +// © AngelaMos | 2026 +// tab-headers.tsx +// =================== + +import { useState } from 'react' +import type { AnalysisResponse, SectionInfo, SegmentInfo } from '@/api' +import { formatHex } from '@/core/lib' +import styles from './analysis.module.scss' + +function PermsBadge({ + r, + w, + x, +}: { + r: boolean + w: boolean + x: boolean +}): React.ReactElement { + return ( + + R + W + X + + ) +} + +function SectionRow({ section }: { section: SectionInfo }): React.ReactElement { + return ( + + {section.name} + {formatHex(section.virtual_address)} + + {section.virtual_size.toLocaleString()} + + {section.raw_size.toLocaleString()} + + + + + ) +} + +function SegmentRow({ segment }: { segment: SegmentInfo }): React.ReactElement { + return ( + + {segment.name ?? '\u2014'} + {formatHex(segment.virtual_address)} + + {segment.virtual_size.toLocaleString()} + + {segment.file_size.toLocaleString()} + + + + + ) +} + +export function TabHeaders({ + data, +}: { + data: AnalysisResponse +}): React.ReactElement { + const [showSegments, setShowSegments] = useState(false) + const fmt = data.passes.format + + if (!fmt) { + return ( +
+ No format data available +
+ ) + } + + return ( +
+
+ FORMAT INFO +
+
+ FORMAT + {fmt.format} +
+
+ ARCH + + {typeof fmt.architecture === 'string' + ? fmt.architecture + : fmt.architecture.Other} + +
+
+ BITS + {fmt.bits} +
+
+ ENDIAN + {fmt.endianness} +
+
+ ENTRY + + {formatHex(fmt.entry_point)} + +
+
+ STRIPPED + + {fmt.is_stripped ? 'YES' : 'NO'} + +
+
+ PIE + + {fmt.is_pie ? 'YES' : 'NO'} + +
+
+ DEBUG + + {fmt.has_debug_info ? 'YES' : 'NO'} + +
+
+
+ + {fmt.pe_info && ( +
+ PE INFO +
+
+ IMAGE BASE + + {formatHex(fmt.pe_info.image_base)} + +
+
+ SUBSYSTEM + + {fmt.pe_info.subsystem} + +
+
+ LINKER + + {fmt.pe_info.linker_version} + +
+
+ ASLR + + {fmt.pe_info.dll_characteristics.aslr ? 'YES' : 'NO'} + +
+
+ DEP + + {fmt.pe_info.dll_characteristics.dep ? 'YES' : 'NO'} + +
+
+ CFG + + {fmt.pe_info.dll_characteristics.cfg ? 'YES' : 'NO'} + +
+
+
+ )} + + {fmt.elf_info && ( +
+ ELF INFO +
+
+ OS ABI + {fmt.elf_info.os_abi} +
+
+ TYPE + + {fmt.elf_info.elf_type} + +
+
+ RELRO + + {fmt.elf_info.gnu_relro ? 'FULL' : 'NO'} + +
+
+ BIND NOW + + {fmt.elf_info.bind_now ? 'YES' : 'NO'} + +
+
+ NX STACK + + {fmt.elf_info.stack_executable ? 'EXEC' : 'NO-EXEC'} + +
+
+ {fmt.elf_info.needed_libraries.length > 0 && ( +
+ LIBRARIES + {fmt.elf_info.needed_libraries.map((lib) => ( + + {lib} + + ))} +
+ )} +
+ )} + + {fmt.macho_info && ( +
+ MACH-O INFO +
+
+ FILE TYPE + + {fmt.macho_info.file_type} + +
+
+ UNIVERSAL + + {fmt.macho_info.is_universal ? 'YES' : 'NO'} + +
+
+ CODE SIGN + + {fmt.macho_info.has_code_signature ? 'YES' : 'NO'} + +
+
+
+ )} + +
+ + SECTIONS ({fmt.sections.length}) + +
+ + + + + + + + + + + + {fmt.sections.map((sec) => ( + + ))} + +
NAMEVADDRVSIZERAW SIZEPERMS
+
+
+ + {fmt.segments.length > 0 && ( +
+ + {showSegments && ( +
+ + + + + + + + + + + + {fmt.segments.map((seg, i) => ( + + ))} + +
NAMEVADDRVSIZEFSIZEPERMS
+
+ )} +
+ )} +
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-imports.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-imports.tsx new file mode 100644 index 0000000..174a301 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-imports.tsx @@ -0,0 +1,205 @@ +// =================== +// © AngelaMos | 2026 +// tab-imports.tsx +// =================== + +import { useState } from 'react' +import type { AnalysisResponse, ImportEntry } from '@/api' +import { formatHex } from '@/core/lib' +import styles from './analysis.module.scss' + +function ImportRow({ entry }: { entry: ImportEntry }): React.ReactElement { + return ( + + {entry.function} + + {entry.address !== null ? formatHex(entry.address) : '\u2014'} + + + {entry.ordinal !== null ? `#${entry.ordinal}` : '\u2014'} + + + {entry.threat_tags.length > 0 && ( +
+ {entry.threat_tags.map((tag) => ( + + {tag} + + ))} +
+ )} + + + ) +} + +function LibraryGroup({ + library, + imports, + isOpen, + onToggle, +}: { + library: string + imports: ImportEntry[] + isOpen: boolean + onToggle: () => void +}): React.ReactElement { + const suspiciousCount = imports.filter((i) => i.is_suspicious).length + return ( +
+ + {isOpen && ( +
+ + + + + + + + + + + {imports.map((entry, i) => ( + + ))} + +
FUNCTIONADDRESSORDINALTAGS
+
+ )} +
+ ) +} + +export function TabImports({ + data, +}: { + data: AnalysisResponse +}): React.ReactElement { + const imp = data.passes.imports + const [openLibs, setOpenLibs] = useState>(new Set()) + + if (!imp) { + return ( +
+ No import data available +
+ ) + } + + const toggleLib = (lib: string) => { + setOpenLibs((prev) => { + const next = new Set(prev) + if (next.has(lib)) next.delete(lib) + else next.add(lib) + return next + }) + } + + const importsByLib = new Map() + for (const entry of imp.imports) { + const list = importsByLib.get(entry.library) ?? [] + list.push(entry) + importsByLib.set(entry.library, list) + } + + return ( +
+ {imp.suspicious_combinations.length > 0 && ( +
+ SUSPICIOUS COMBINATIONS +
+ {imp.suspicious_combinations.map((combo) => ( +
+
+ {combo.name} + {combo.mitre_id} + + {combo.severity} + +
+ {combo.description} +
+ {combo.apis.map((api) => ( + + {api} + + ))} +
+
+ ))} +
+
+ )} + +
+ + IMPORTS ({imp.statistics.total_imports}) + + {Array.from(importsByLib.entries()).map(([lib, imports]) => ( + toggleLib(lib)} + /> + ))} +
+ + {imp.exports.length > 0 && ( +
+ + EXPORTS ({imp.exports.length}) + +
+ + + + + + + + + + + {imp.exports.map((exp, i) => ( + + + + + + + ))} + +
NAMEADDRESSORDINALFORWARD
{exp.name ?? '\u2014'}{formatHex(exp.address)} + {exp.ordinal !== null ? `#${exp.ordinal}` : '\u2014'} + + {exp.forward_target ?? '\u2014'} +
+
+
+ )} +
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-overview.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-overview.tsx new file mode 100644 index 0000000..77ae4a2 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-overview.tsx @@ -0,0 +1,144 @@ +// =================== +// © AngelaMos | 2026 +// tab-overview.tsx +// =================== + +import type { AnalysisResponse } from '@/api' +import styles from './analysis.module.scss' + +const MITRE_BASE_URL = 'https://attack.mitre.org/techniques/' + +function formatMitreUrl(id: string): string { + return `${MITRE_BASE_URL}${id.replace('.', '/')}` +} + +export function TabOverview({ + data, +}: { + data: AnalysisResponse +}): React.ReactElement { + const { passes } = data + + return ( +
+
+ {passes.format && ( +
+ FORMAT + + {passes.format.format} / {passes.format.bits}-bit + + + {passes.format.sections.length} sections,{' '} + {passes.format.segments.length} segments + +
+ )} + + {passes.imports && ( +
+ IMPORTS + + {passes.imports.statistics.total_imports} across{' '} + {passes.imports.statistics.library_count} libraries + + + {passes.imports.statistics.suspicious_count} suspicious + +
+ )} + + {passes.strings && ( +
+ STRINGS + + {passes.strings.statistics.total} extracted + + + {passes.strings.statistics.suspicious_count} suspicious + +
+ )} + + {passes.entropy && ( +
+ ENTROPY + + {passes.entropy.overall_entropy.toFixed(2)} overall + + + {passes.entropy.packing_detected + ? `Packing detected${passes.entropy.packer_name ? `: ${passes.entropy.packer_name}` : ''}` + : 'No packing detected'} + +
+ )} + + {passes.disassembly && ( +
+ DISASSEMBLY + + {passes.disassembly.total_functions} functions + + + {passes.disassembly.total_instructions} instructions + +
+ )} + + {passes.threat && ( +
+ YARA + + {passes.threat.yara_matches.length} rule matches + + {passes.threat.summary} +
+ )} +
+ + {passes.format && passes.format.anomalies.length > 0 && ( +
+ ANOMALIES +
+ {passes.format.anomalies.map((anomaly, i) => ( +
+ {typeof anomaly === 'string' ? ( + {anomaly} + ) : ( + Object.entries(anomaly).map(([key, val]) => ( + + {key}: {String(val)} + + )) + )} +
+ ))} +
+
+ )} + + {passes.threat && passes.threat.mitre_techniques.length > 0 && ( +
+ MITRE ATT&CK + +
+ )} +
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-strings.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-strings.tsx new file mode 100644 index 0000000..f7a3438 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/analysis/tab-strings.tsx @@ -0,0 +1,233 @@ +// =================== +// © AngelaMos | 2026 +// tab-strings.tsx +// =================== + +import { useMemo, useState } from 'react' +import type { + AnalysisResponse, + ExtractedString, + StringCategory, + StringEncoding, +} from '@/api' +import { formatHex } from '@/core/lib' +import styles from './analysis.module.scss' + +const ENCODING_OPTIONS: readonly ('All' | StringEncoding)[] = [ + 'All', + 'Ascii', + 'Utf8', + 'Utf16Le', +] as const + +const CATEGORY_OPTIONS: readonly ('All' | StringCategory)[] = [ + 'All', + 'Url', + 'IpAddress', + 'FilePath', + 'RegistryKey', + 'ShellCommand', + 'CryptoWallet', + 'Email', + 'SuspiciousApi', + 'PackerSignature', + 'DebugArtifact', + 'AntiAnalysis', + 'PersistencePath', + 'EncodedData', + 'Generic', +] as const + +const PAGE_SIZE = 50 + +function StringRow({ + str, + expanded, + onToggle, +}: { + str: ExtractedString + expanded: boolean + onToggle: () => void +}): React.ReactElement { + const MAX_DISPLAY = 80 + const needsTruncate = str.value.length > MAX_DISPLAY + const display = + expanded || !needsTruncate + ? str.value + : `${str.value.slice(0, MAX_DISPLAY)}\u2026` + + return ( + + {formatHex(str.offset)} + + + + {str.encoding} + + {str.category} + + {str.section ?? '\u2014'} + + ) +} + +export function TabStrings({ + data, +}: { + data: AnalysisResponse +}): React.ReactElement { + const str = data.passes.strings + const [search, setSearch] = useState('') + const [encoding, setEncoding] = useState<'All' | StringEncoding>('All') + const [category, setCategory] = useState<'All' | StringCategory>('All') + const [suspiciousOnly, setSuspiciousOnly] = useState(false) + const [page, setPage] = useState(0) + const [expandedRows, setExpandedRows] = useState>(new Set()) + + const filtered = useMemo(() => { + if (!str) return [] + return str.strings.filter((s) => { + if (search && !s.value.toLowerCase().includes(search.toLowerCase())) + return false + if (encoding !== 'All' && s.encoding !== encoding) return false + if (category !== 'All' && s.category !== category) return false + if (suspiciousOnly && !s.is_suspicious) return false + return true + }) + }, [str, search, encoding, category, suspiciousOnly]) + + if (!str) { + return ( +
+ No string data available +
+ ) + } + + const totalPages = Math.ceil(filtered.length / PAGE_SIZE) + const pageStrings = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE) + + const toggleRow = (offset: number) => { + setExpandedRows((prev) => { + const next = new Set(prev) + if (next.has(offset)) next.delete(offset) + else next.add(offset) + return next + }) + } + + return ( +
+
+ { + setSearch(e.target.value) + setPage(0) + }} + /> + + + +
+ + + {filtered.length} / {str.statistics.total} strings + + +
+ + + + + + + + + + + + {pageStrings.map((s) => ( + toggleRow(s.offset)} + /> + ))} + +
OFFSETVALUEENCODINGCATEGORYSECTION
+
+ + {totalPages > 1 && ( +
+ + + {page + 1} / {totalPages} + + +
+ )} +
+ ) +} diff --git a/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/landing/index.tsx b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/landing/index.tsx new file mode 100644 index 0000000..8779d86 --- /dev/null +++ b/PROJECTS/intermediate/binary-analysis-tool/frontend/src/pages/landing/index.tsx @@ -0,0 +1,205 @@ +// =================== +// © AngelaMos | 2026 +// index.tsx +// =================== + +import { useCallback, useRef, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { toast } from 'sonner' +import { useUpload } from '@/api' +import { formatBytes } from '@/core/lib' +import styles from './landing.module.scss' + +const HEX_OFFSETS = Array.from( + { length: 16 }, + (_, i) => `0x${(i * 16).toString(16).toUpperCase().padStart(4, '0')}` +) + +const ANALYSIS_PASSES = [ + { id: '01', name: 'FORMAT' }, + { id: '02', name: 'IMPORTS' }, + { id: '03', name: 'STRINGS' }, + { id: '04', name: 'ENTROPY' }, + { id: '05', name: 'DISASM' }, + { id: '06', name: 'THREAT' }, +] as const + +export function Component(): React.ReactElement { + const [file, setFile] = useState(null) + const [isDragging, setIsDragging] = useState(false) + const inputRef = useRef(null) + const navigate = useNavigate() + const upload = useUpload() + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault() + e.dataTransfer.dropEffect = 'copy' + }, []) + + const handleDragEnter = useCallback((e: React.DragEvent) => { + e.preventDefault() + setIsDragging(true) + }, []) + + const handleDragLeave = useCallback((e: React.DragEvent) => { + const related = e.relatedTarget as Node | null + if (related && e.currentTarget.contains(related)) return + setIsDragging(false) + }, []) + + const handleDrop = useCallback((e: React.DragEvent) => { + e.preventDefault() + setIsDragging(false) + const droppedFile = e.dataTransfer.files[0] + if (droppedFile) setFile(droppedFile) + }, []) + + const handleFileSelect = useCallback( + (e: React.ChangeEvent) => { + const selected = e.target.files?.[0] + if (selected) setFile(selected) + }, + [] + ) + + const handleSubmit = useCallback(() => { + if (!file) return + upload.mutate(file, { + onSuccess: (data) => navigate(`/analysis/${data.slug}`), + onError: () => toast.error('Failed to analyze binary'), + }) + }, [file, upload, navigate]) + + const handleClear = useCallback(() => { + setFile(null) + upload.reset() + if (inputRef.current) inputRef.current.value = '' + }, [upload]) + + return ( +
+ + +