diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..a73df9f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..f6834a5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,82 @@
+name: Bug report
+description: Report a bug or unexpected behavior in Ghostline
+title: "[Bug] "
+labels: ["bug"]
+body:
+ - type: textarea
+ id: summary
+ attributes:
+ label: Summary
+ description: One or two sentences describing the bug.
+ placeholder: "RID enumeration crashes when target is unreachable."
+ validations:
+ required: true
+
+ - type: input
+ id: version
+ attributes:
+ label: Ghostline version
+ description: "Commit SHA or release tag. Run `git rev-parse --short HEAD` in the repo root."
+ placeholder: "e.g. v1.0.0 or abc1234"
+ validations:
+ required: true
+
+ - type: input
+ id: os
+ attributes:
+ label: Operating system
+ description: "Distribution and version."
+ placeholder: "e.g. Kali Linux 2025.3"
+ validations:
+ required: true
+
+ - type: dropdown
+ id: module
+ attributes:
+ label: Affected module
+ options:
+ - lib/core.sh
+ - lib/ui.sh
+ - lib/installer.sh
+ - lib/modules/config.sh
+ - lib/modules/passive.sh
+ - lib/modules/active.sh
+ - lib/modules/special.sh
+ - install.sh
+ - ghostline.sh (entry point)
+ - Not sure
+ validations:
+ required: true
+
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce
+ description: Exact menu path, inputs, and target configuration.
+ placeholder: |
+ 1. Launch ./ghostline.sh
+ 2. Main Menu → [3] Active Enumeration → [5] RID Enumeration
+ 3. Target was set to 10.10.10.10 (offline)
+ validations:
+ required: true
+
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected behavior
+ validations:
+ required: true
+
+ - type: textarea
+ id: actual
+ attributes:
+ label: Actual behavior
+ description: Paste error output. Use code fences (```) for logs.
+ validations:
+ required: true
+
+ - type: textarea
+ id: extra
+ attributes:
+ label: Additional context
+ description: Logs, screenshots, related issues — anything else that helps.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..4a1cd3b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Security vulnerability
+ url: https://github.com/WhiteMuush/Ghostline/security/advisories/new
+ about: Report a security issue privately via GitHub Security Advisories.
diff --git a/.github/ISSUE_TEMPLATE/tool_request.yml b/.github/ISSUE_TEMPLATE/tool_request.yml
new file mode 100644
index 0000000..ee54279
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/tool_request.yml
@@ -0,0 +1,57 @@
+name: Tool request
+description: Propose a new tool or module for Ghostline
+title: "[Tool] "
+labels: ["enhancement", "tool-request"]
+body:
+ - type: input
+ id: tool_name
+ attributes:
+ label: Tool name
+ placeholder: "e.g. certipy"
+ validations:
+ required: true
+
+ - type: input
+ id: tool_url
+ attributes:
+ label: Upstream URL
+ placeholder: "https://github.com/ly4k/Certipy"
+ validations:
+ required: true
+
+ - type: textarea
+ id: rationale
+ attributes:
+ label: Why this tool fits Ghostline
+ description: How does it help with Active Directory enumeration or exploitation?
+ validations:
+ required: true
+
+ - type: dropdown
+ id: target_module
+ attributes:
+ label: Suggested module placement
+ options:
+ - lib/modules/passive.sh
+ - lib/modules/active.sh
+ - lib/modules/special.sh
+ - New module
+ validations:
+ required: true
+
+ - type: textarea
+ id: install_hint
+ attributes:
+ label: Installation hint
+ description: How is the tool installed on Debian/Kali? (apt, pipx, git clone, ...)
+ placeholder: |
+ pipx install certipy-ad
+ validations:
+ required: true
+
+ - type: textarea
+ id: example_usage
+ attributes:
+ label: Example invocation
+ description: A representative command, ideally usable as the body of the new module function.
+ render: bash
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..4f7ef37
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,32 @@
+
+
+## Summary
+
+
+
+## Type of change
+
+- [ ] New tool / module
+- [ ] Bug fix
+- [ ] Refactor / cleanup
+- [ ] Documentation
+- [ ] CI / tooling
+
+## Checklist
+
+- [ ] `bash -n` passes on every modified `.sh`
+- [ ] `shellcheck` passes (CI will verify)
+- [ ] `lib/` source chain still loads (smoke test in CI)
+- [ ] No French text introduced (project is English-only)
+- [ ] If adding a tool: I followed `docs/ADDING_A_TOOL.md`
+- [ ] `README.md` updated if the menus, CLI or install steps changed
+
+## Test plan
+
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d8bf675
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,99 @@
+name: ci
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ shellcheck:
+ name: ShellCheck
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Run shellcheck
+ uses: ludeeus/action-shellcheck@master
+ env:
+ SHELLCHECK_OPTS: -e SC1091 -e SC2034 -e SC2154
+ with:
+ severity: warning
+ ignore_paths: >-
+ graphify-out
+
+ syntax:
+ name: bash -n
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Syntax check every shell script
+ shell: bash
+ run: |
+ set -euo pipefail
+ failed=0
+ while IFS= read -r -d '' file; do
+ if ! bash -n "$file"; then
+ echo "SYNTAX ERROR: $file"
+ failed=1
+ fi
+ done < <(find . -name '*.sh' -not -path './.git/*' -print0)
+ exit "$failed"
+
+ smoke:
+ name: Source chain smoke test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Verify expected functions are exported
+ shell: bash
+ run: |
+ set -uo pipefail
+ # shellcheck disable=SC1091
+ source ./lib/core.sh
+ # shellcheck disable=SC1091
+ source ./lib/installer.sh
+ # shellcheck disable=SC1091
+ source ./lib/ui.sh
+ # shellcheck disable=SC1091
+ source ./lib/modules/config.sh
+ # shellcheck disable=SC1091
+ source ./lib/modules/passive.sh
+ # shellcheck disable=SC1091
+ source ./lib/modules/active.sh
+ # shellcheck disable=SC1091
+ source ./lib/modules/special.sh
+
+ expected=(
+ log_step log_info log_warn log_error log_success
+ prompt_value prompt_password prompt_yesno press_enter_to_continue
+ ensure_command resolve_command
+ clone_or_pull pip_install pipx_install apt_install
+ generate_main_menu generate_config_menu generate_passive_menu
+ generate_active_menu generate_special_menu
+ display_banner_with_menu display_title_middle_screen prompt_menu_choice
+ require_target require_domain require_credentials ensure_output_dir
+ handle_config_menu handle_passive_menu handle_active_menu handle_special_menu
+ passive_run_nmap passive_run_enum4linux passive_run_rpc
+ passive_run_ldap passive_run_dns
+ active_run_bloodhound active_run_cme active_run_adidns
+ active_run_getnpusers active_run_ridenum
+ special_run_workflow special_run_smb_vulns special_run_secretsdump
+ special_view_results
+ )
+
+ missing=0
+ for fn in "${expected[@]}"; do
+ if ! declare -F "$fn" >/dev/null; then
+ echo "MISSING: $fn"
+ missing=$((missing+1))
+ fi
+ done
+
+ if (( missing > 0 )); then
+ echo "Smoke test FAILED: $missing function(s) missing"
+ exit 1
+ fi
+ echo "Smoke test PASSED — ${#expected[@]} functions present"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..271bda7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+# Enumeration output directories
+ad_enum_*/
+output/
+results/
+
+# Editor / IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+*~
+.DS_Store
+Thumbs.db
+
+# Python (in case modules grow Python tooling later)
+__pycache__/
+*.pyc
+*.pyo
+.venv/
+venv/
+*.egg-info/
+
+# Logs and tmp
+*.log
+*.tmp
+*.bak
+
+# Tooling internals (project-local Claude instructions, graph dumps)
+CLAUDE.md
+graphify-out/
+
+# Credentials and secrets — defensive, never commit
+*.key
+*.pem
+*.crt
+.env
+.env.*
+!.env.example
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..7921489
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,13 @@
+# Code of Conduct
+
+This project adopts the **Contributor Covenant**, version 2.1.
+
+The full text is published at
+.
+
+By participating in this project (issues, pull requests, discussions)
+you agree to abide by its terms.
+
+To report a concern, contact the maintainer via the email address on
+their GitHub profile, or privately through
+[GitHub Security Advisories](https://github.com/WhiteMuush/Ghostline/security/advisories/new).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..3bec656
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,129 @@
+# Contributing to Ghostline
+
+Thanks for the interest. Ghostline is an interactive Active Directory
+enumeration toolkit built on top of well-known security tools. The codebase
+is plain Bash and aims to stay small, readable and contributor-friendly.
+
+This guide covers the conventions that make a contribution easy to review
+and merge.
+
+---
+
+## Local setup
+
+```bash
+git clone https://github.com/WhiteMuush/Ghostline.git
+cd Ghostline
+sudo ./install.sh # installs every supported tool system-wide
+./ghostline.sh # launch the interactive menu
+```
+
+Ghostline targets **Debian / Ubuntu / Kali**. Other distros may work but
+are not part of CI.
+
+Optional local checks before opening a PR:
+
+```bash
+# Syntax check on every bash script
+find . -name '*.sh' -not -path './.git/*' -exec bash -n {} \;
+
+# Shellcheck (matches what CI runs)
+shellcheck -e SC1091 -e SC2034 -e SC2154 \
+ ghostline.sh install.sh lib/*.sh lib/modules/*.sh
+```
+
+---
+
+## Project layout
+
+```
+ghostline.sh Thin entry point; loads lib/ and drives the loop.
+install.sh Installs every supported tool. Run with sudo.
+lib/core.sh Colors (TTY-aware), globals, palette.
+lib/ui.sh ASCII art and menu rendering.
+lib/installer.sh Logging, prompting and install primitives.
+lib/modules/config.sh Target / domain / credentials / output config.
+lib/modules/passive.sh Unauthenticated reconnaissance.
+lib/modules/active.sh Authenticated enumeration.
+lib/modules/special.sh Workflows, vuln scans, secrets dump.
+```
+
+See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full description
+and [docs/ADDING_A_TOOL.md](docs/ADDING_A_TOOL.md) if you want to plug in
+a new tool.
+
+---
+
+## Code conventions
+
+### Shell
+
+- `#!/usr/bin/env bash` shebang on every executable script.
+- Strict mode at the entry point only: `set -uo pipefail` for the
+ interactive `ghostline.sh`, `set -euo pipefail` for the non-interactive
+ `install.sh`. **Do not** add `set -e` to interactive menus — a single
+ non-zero exit code kills the whole loop.
+- Always quote variable expansions: `"${var}"`, not `$var`.
+- Function names are `snake_case` and prefixed by their module:
+ `passive_run_nmap`, `active_run_cme`, etc.
+- Global state lives in `GHOSTLINE_*` variables defined in `lib/core.sh`.
+- Logging goes through `log_step / log_info / log_warn / log_error /
+ log_success`. Do not emit raw `echo -e ${RED}...${RESET}` from
+ application code.
+- User prompts go through `prompt_value / prompt_password / prompt_yesno`.
+- Tool presence is checked with `ensure_command` (and `resolve_command`
+ for binaries with multiple possible names).
+- Use `mapfile -t arr <<<"$STRING"` or `mapfile -t arr < <(cmd)` for
+ array splitting. Avoid the legacy `IFS=$'\n' read -r -d '' -a` pattern.
+
+### Language
+
+Every file in the repository is **English only** — code, comments, log
+messages, prompts, README, docs, commit messages. PRs that introduce
+non-English content will be asked to translate before merge.
+
+### Comments
+
+Default to writing no comments. Only add one when the *why* is
+non-obvious. Don't restate what well-named code already says.
+
+### Commit messages
+
+Conventional Commits:
+
+```
+type: short imperative summary
+```
+
+Common types: `feat`, `fix`, `refactor`, `docs`, `ci`, `chore`, `test`.
+
+---
+
+## CI
+
+Every PR runs three checks:
+
+1. **shellcheck** with warning severity and a small ignore list
+ (`SC1091`, `SC2034`, `SC2154`).
+2. **bash -n** on every `.sh` for syntax.
+3. **Smoke test** that sources the full `lib/` chain and asserts that
+ every public function is defined.
+
+All three must pass before review.
+
+---
+
+## Reporting bugs and proposing tools
+
+- Bugs: open an issue with the **Bug report** template.
+- New tools: open an issue with the **Tool request** template. Bonus
+ points for opening the PR that wires the tool in.
+- Security issues: see [SECURITY.md](SECURITY.md).
+
+---
+
+## Authorized use only
+
+Ghostline is for **authorized security testing** (pentest engagements,
+CTFs, lab environments). Don't open issues asking for help against
+networks you do not own or have written permission to test.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..45d8f66
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Melvin PETIT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bb898cc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+**Ghostline** is an interactive bash toolkit that automates Active Directory
+enumeration by integrating 10+ professional security tools into a single,
+easy-to-use menu. It supports both passive reconnaissance (no credentials)
+and active enumeration (with credentials).
+
+## Features
+
+### Configuration management
+- Persistent target configuration (IP / hostname, domain, credentials).
+- Custom output directory naming.
+- Configuration displayed in every menu header.
+
+### Passive enumeration (no credentials required)
+- Network scanning (Nmap).
+- SMB enumeration (enum4linux-ng).
+- RPC null session attacks (rpcclient).
+- Anonymous LDAP queries (ldapsearch).
+- DNS enumeration (dnsrecon).
+
+### Active enumeration (credentials required)
+- BloodHound data collection.
+- Comprehensive SMB enumeration (CrackMapExec).
+- AD-integrated DNS dumping (adidnsdump).
+- Kerberos pre-auth attacks (GetNPUsers).
+- RID cycling enumeration (ridenum).
+
+### Special actions
+- Automated full workflow.
+- SMB vulnerability scanning.
+- Domain secrets extraction (secretsdump).
+- Results viewer.
+
+---
+
+
+
+## Installation
+
+### Prerequisites
+
+Ghostline targets Debian / Ubuntu / Kali and bundles an installer for every
+supported tool:
+
+```bash
+sudo ./install.sh
+```
+
+Or install manually:
+
+```bash
+# Debian / Ubuntu / Kali
+sudo apt update
+sudo apt install -y \
+ nmap \
+ samba-common-bin \
+ ldap-utils \
+ dnsrecon \
+ python3 \
+ python3-pip \
+ pipx
+
+# Python tools
+pipx install crackmapexec
+pipx install bloodhound
+pipx install impacket
+
+# GitHub-hosted tools
+git clone https://github.com/cddmp/enum4linux-ng.git /opt/enum4linux-ng
+git clone https://github.com/dirkjanm/adidnsdump.git /opt/adidnsdump
+git clone https://github.com/trustedsec/ridenum.git /opt/ridenum
+```
+
+### Installing Ghostline
+
+```bash
+git clone https://github.com/WhiteMuush/Ghostline.git
+cd Ghostline
+chmod +x ghostline.sh
+./ghostline.sh
+```
+
+---
+
+## Quick start
+
+### Basic usage
+
+```bash
+./ghostline.sh
+
+# 1. Configure your target
+Main Menu → [1] Configuration Menu
+ → [1] Set Target: 192.168.1.10
+ → [2] Set Domain: corp.local
+ → [0] Back
+
+# 2. Run automated reconnaissance
+Main Menu → [4] Special Actions
+ → [1] Auto Workflow
+
+# 3. View results
+Main Menu → [4] Special Actions
+ → [4] View Results
+```
+
+### With credentials
+
+```bash
+# 1. Configure credentials
+Main Menu → [1] Configuration Menu
+ → [3] Set Credentials
+ Username: john.doe
+ Password: ********
+
+# 2. Run BloodHound collection
+Main Menu → [3] Active Enumeration
+ → [1] BloodHound Collection
+
+# Results saved in: ad_enum_YYYYMMDD_HHMMSS/
+```
+
+---
+
+## Project layout
+
+```
+ghostline.sh Entry point (~50 lines).
+install.sh Installs every supported tool.
+lib/
+├── core.sh Colors (TTY-aware), palette, globals.
+├── ui.sh ASCII art and menu rendering.
+├── installer.sh Logging, prompting, install primitives.
+└── modules/
+ ├── config.sh Target / domain / credentials / output.
+ ├── passive.sh Unauthenticated reconnaissance.
+ ├── active.sh Authenticated enumeration.
+ └── special.sh Workflows, vuln scans, secrets dump.
+docs/
+├── ARCHITECTURE.md Layout, boot sequence, helpers, CI.
+└── ADDING_A_TOOL.md Recipe for plugging in a new tool.
+.github/
+├── workflows/ci.yml shellcheck + bash -n + smoke test.
+├── ISSUE_TEMPLATE/ Structured bug and tool-request forms.
+└── PULL_REQUEST_TEMPLATE.md
+```
+
+See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for details and
+[CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.
+
+---
+
+## Output structure
+
+All results are saved in a timestamped directory:
+
+```
+ad_enum_20231220_143022/
+├── nmap_ad.nmap Nmap normal output
+├── nmap_ad.xml Nmap XML (importable)
+├── nmap_ad.gnmap Nmap greppable
+├── enum4linux-ng.txt Full SMB enumeration
+├── rpcclient.txt RPC enumeration results
+├── ldap.txt LDAP query results
+├── dnsrecon.txt DNS records
+├── cme_shares.txt CrackMapExec shares
+├── cme_users.txt CrackMapExec users
+├── dns.csv AD-integrated DNS dump
+├── asreproast.txt AS-REP roastable accounts
+├── ridenum.txt RID enumeration
+├── smb_vulns.nmap SMB vulnerability scan
+├── secrets.txt Domain secrets (NTLM hashes)
+└── *.json BloodHound data files
+```
+
+### Importing results
+
+**BloodHound:**
+
+```bash
+neo4j console
+# Then in BloodHound GUI: Upload Data → select the .json files
+```
+
+**Nmap XML:**
+
+```bash
+xsltproc nmap_ad.xml -o report.html
+nmap -iL nmap_ad.xml --resume
+```
+
+---
+
+## Contributing
+
+Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the
+local setup, the conventions and the PR checklist. To plug in a new tool,
+[docs/ADDING_A_TOOL.md](docs/ADDING_A_TOOL.md) walks through the recipe in
+under a page.
+
+- Bug reports and tool requests use the templates in
+ [.github/ISSUE_TEMPLATE/](.github/ISSUE_TEMPLATE/).
+- Security issues should be reported privately — see
+ [SECURITY.md](SECURITY.md).
+
+---
+
+## Tools integrated
+
+- [Nmap](https://github.com/nmap/nmap) — by Gordon Lyon
+ Network discovery and security auditing tool. Used with NSE scripts for
+ SMB, LDAP, Kerberos and AD enumeration.
+- [enum4linux-ng](https://github.com/cddmp/enum4linux-ng) — by cddmp
+ Modern SMB enumeration tool (users, groups, shares, policies).
+- [ldapsearch (OpenLDAP)](https://git.openldap.org/openldap/openldap)
+ Native LDAP query utility for extracting domain objects and attributes.
+- [rpcclient (Samba)](https://github.com/samba-team/samba)
+ RPC interaction tool for querying domain users, groups and SIDs via SMB.
+- [CrackMapExec](https://github.com/Porchetta-Industries/CrackMapExec) — by byt3bl33d3r
+ Swiss army knife for Active Directory: SMB, LDAP, WinRM, MSSQL, and more.
+- [Impacket](https://github.com/SecureAuthCorp/impacket) — by SecureAuth
+ Collection of Python scripts for low-level network protocol interaction.
+ Includes `GetUserSPNs.py` and `secretsdump.py`.
+- [BloodHound](https://github.com/BloodHoundAD/BloodHound) — by SpecterOps
+ Graph-based Active Directory attack path analysis. Uses bloodhound-python
+ as the data ingestor.
+- [bloodhound-python](https://github.com/fox-it/BloodHound.py) — data ingestor
+ CLI collector used by BloodHound.
+- [adidnsdump](https://github.com/dirkjanm/adidnsdump) — by dirkjanm
+ Enumerates Active Directory–integrated DNS records via LDAP.
+- [ridenum](https://github.com/trustedsec/ridenum) — by TrustedSec
+ RID cycling tool for enumerating domain users.
+- [dnsrecon](https://github.com/darkoperator/dnsrecon) — by DarkOperator
+ DNS reconnaissance tool (alternative: dnsenum).
+- [Kerbrute](https://github.com/ropnop/kerbrute) — by ropnop
+ Kerberos-based user enumeration and password spraying tool.
+- [ldapdomaindump](https://github.com/dirkjanm/ldapdomaindump) — by dirkjanm
+ Dumps LDAP domain information into human-readable reports.
+
+---
+
+## License
+
+Ghostline is released under the [MIT License](LICENSE). Use only against
+systems you own or have explicit written permission to test.
diff --git a/Readme.md b/Readme.md
deleted file mode 100644
index 6ae91ec..0000000
--- a/Readme.md
+++ /dev/null
@@ -1,232 +0,0 @@
-
-
-**GhostLine** is an interactive bash toolkit that automates Active Directory enumeration by integrating 10+ professional security tools into a beautiful, easy-to-use interface. Inspired by the aesthetics of "Feed Your Spider", it provides both passive and active reconnaissance capabilities.
-
-## Features
-
-### Configuration Management
-- Persistent target configuration (IP/Hostname, Domain, Credentials)
-- Custom output directory naming
-- Configuration displayed in all menus
-
-### Passive Enumeration (No Credentials Required)
-- Network scanning (Nmap)
-- SMB enumeration (enum4linux-ng)
-- RPC null session attacks (rpcclient)
-- Anonymous LDAP queries (ldapsearch)
-- DNS enumeration (dnsrecon)
-
-### Active Enumeration (Credentials Required)
-- BloodHound data collection
-- Comprehensive SMB enumeration (CrackMapExec)
-- AD-integrated DNS dumping (adidnsdump)
-- Kerberos pre-auth attacks (GetNPUsers)
-- RID cycling enumeration (ridenum)
-
-### Special Actions
-- Automated full workflow
-- SMB vulnerability scanning
-- Domain secrets extraction (secretsdump)
-- Results viewer
-
----
-
-
-
-
-## Installation
-
-### Prerequisites
-
-GhostLine requires the following tools to be installed:
-
-```bash
-# Install all tools at once with the installation script
-sudo ./install.sh
-```
-
-Or install manually:
-
-```bash
-# Debian/Ubuntu/Kali
-sudo apt update
-sudo apt install -y \
- nmap \
- samba-common-bin \
- ldap-utils \
- dnsrecon \
- python3 \
- python3-pip \
- pipx
-
-# Install Python tools
-pipx install crackmapexec
-pipx install bloodhound
-pipx install impacket
-
-# Install from GitHub
-git clone https://github.com/cddmp/enum4linux-ng.git /opt/enum4linux-ng
-git clone https://github.com/dirkjanm/adidnsdump.git /opt/adidnsdump
-git clone https://github.com/trustedsec/ridenum.git /opt/ridenum
-```
-
-### Installing GhostLine
-
-```bash
-# Clone the repository
-git clone https://github.com/WhiteMuush/GhostLine.git
-cd GhostLine
-
-# Make executable
-chmod +x ghostline.sh
-
-# Run
-./ghostline.sh
-```
-
----
-
-## Quick Start
-
-### Basic Usage
-
-```bash
-# Launch GhostLine
-./ghostline.sh
-
-# 1. Configure your target
-Main Menu → [1] Configuration Menu
- → [1] Set Target: 192.168.1.10
- → [2] Set Domain: corp.local
- → [0] Back
-
-# 2. Run automated reconnaissance
-Main Menu → [4] Special Actions
- → [1] Auto Workflow
-
-# 3. View results
-Main Menu → [4] Special Actions
- → [4] View Results
-```
-
-### With Credentials
-
-```bash
-# 1. Configure credentials
-Main Menu → [1] Configuration Menu
- → [3] Set Credentials
- Username: john.doe
- Password: ********
-
-# 2. Run BloodHound collection
-Main Menu → [3] Active Enumeration
- → [1] BloodHound Collection
-
-# Results saved in: ad_enum_YYYYMMDD_HHMMSS/
-```
-
----
-
-## Output Structure
-
-All results are saved in a timestamped directory:
-
-```
-ad_enum_20231220_143022/
-├── nmap_ad.nmap # Nmap normal output
-├── nmap_ad.xml # Nmap XML (importable)
-├── nmap_ad.gnmap # Nmap greppable
-├── enum4linux-ng.txt # Full SMB enumeration
-├── rpcclient.txt # RPC enumeration results
-├── ldap.txt # LDAP query results
-├── dnsrecon.txt # DNS records
-├── cme_shares.txt # CrackMapExec shares
-├── cme_users.txt # CrackMapExec users
-├── dns.csv # AD-integrated DNS dump
-├── asreproast.txt # AS-REP roastable accounts
-├── ridenum.txt # RID enumeration
-├── smb_vulns.nmap # SMB vulnerability scan
-├── secrets.txt # Domain secrets (NTLM hashes)
-└── *.json # BloodHound data files
-```
-
-### Importing Results
-
-**BloodHound:**
-```bash
-# Import JSON files into BloodHound
-neo4j console
-# Then in BloodHound GUI: Upload Data → Select .json files
-```
-
-**Nmap XML:**
-```bash
-# Open in various tools
-xsltproc nmap_ad.xml -o report.html
-nmap -iL nmap_ad.xml --resume
-```
-
----
-## Contributing
-
-Contributions are welcome! Here's how you can help:
-
-### Reporting Bugs
-Open an issue with:
-- GhostLine version
-- Operating system
-- Steps to reproduce
-- Expected vs actual behavior
-
-### Suggesting Features
-Open an issue with:
-- Feature description
-- Use case
-- Expected benefits
-
----
-
-### Tools Integrated
-
-- [Nmap](https://github.com/nmap/nmap) — by Gordon Lyon
- Network discovery and security auditing tool. Used with NSE scripts for SMB, LDAP, Kerberos and AD enumeration.
-
-- [enum4linux-ng](https://github.com/cddmp/enum4linux-ng) — by cddmp
- Modern SMB enumeration tool (users, groups, shares, policies).
-
-- [ldapsearch (OpenLDAP)](https://git.openldap.org/openldap/openldap)
- Native LDAP query utility for extracting domain objects and attributes.
-
-- [rpcclient (Samba)](https://github.com/samba-team/samba)
- RPC interaction tool for querying domain users, groups and SIDs via SMB.
-
-- [CrackMapExec](https://github.com/Porchetta-Industries/CrackMapExec) — by byt3bl33d3r
- Swiss army knife for Active Directory: SMB, LDAP, WinRM, MSSQL, and more.
-
-- [Impacket](https://github.com/SecureAuthCorp/impacket) — by SecureAuth Corporation
- Collection of Python scripts for low-level network protocol interaction. Includes tools such as GetUserSPNs.py and secretsdump.py.
-
-- [BloodHound](https://github.com/BloodHoundAD/BloodHound) — by SpecterOps
- Graph-based Active Directory attack path analysis. Uses bloodhound-python as the data ingestor.
-
-- [bloodhound-python](https://github.com/fox-it/BloodHound.py) — data ingestor
- CLI collector used by BloodHound.
-
-- [adidnsdump](https://github.com/dirkjanm/adidnsdump) — by dirkjanm
- Enumerates Active Directory–integrated DNS records via LDAP.
-
-- [ridenum](https://github.com/trustedsec/ridenum) — by TrustedSec
- RID cycling tool for enumerating domain users.
-
-- [dnsrecon](https://github.com/darkoperator/dnsrecon) — by DarkOperator
- DNS reconnaissance tool (alternative: dnsenum).
-
-- [Kerbrute](https://github.com/ropnop/kerbrute) — by ropnop
- Kerberos-based user enumeration and password spraying tool.
-
-- [ldapdomaindump](https://github.com/dirkjanm/ldapdomaindump) — by dirkjanm
- Dumps LDAP domain information into human-readable reports.
-
----
-
-Same script in powershell coming soon !
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..78aa935
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,43 @@
+# Security Policy
+
+Ghostline is an **offensive security toolkit**. It is meant to be run by
+authorized testers against Active Directory environments they own or
+have explicit written permission to test. Misuse is the responsibility
+of the operator.
+
+## Scope of this policy
+
+This policy covers vulnerabilities **in the Ghostline wrapper itself** —
+the entry point, the library, the menus and the installer. Examples:
+
+- Command injection via a configuration field that isn't properly quoted.
+- Path traversal in the output directory handling.
+- Credentials accidentally written to disk or logs.
+- Privilege escalation through the installer.
+
+It does **not** cover vulnerabilities in the third-party tools
+Ghostline wraps (`nmap`, `enum4linux-ng`, `bloodhound-python`,
+`crackmapexec`, `impacket`, etc.). Report those upstream.
+
+## Reporting a vulnerability
+
+**Please do not open a public issue.** Use one of the private channels:
+
+1. [GitHub Security Advisories](https://github.com/WhiteMuush/Ghostline/security/advisories/new)
+ — preferred, lets us collaborate on a fix.
+2. Direct contact via the email address on the maintainer's GitHub
+ profile.
+
+Include:
+
+- The Ghostline version (commit SHA or release tag).
+- A clear description of the issue and the impact.
+- A reproduction recipe — exact menu path, target setup, payload.
+- (Optional) a suggested fix.
+
+## What to expect
+
+- Acknowledgement within 7 days.
+- A discussion of the impact and the proposed fix.
+- Coordinated disclosure once a patch is ready. Credit goes to the
+ reporter unless they prefer to stay anonymous.
diff --git a/docs/ADDING_A_TOOL.md b/docs/ADDING_A_TOOL.md
new file mode 100644
index 0000000..0ac61ce
--- /dev/null
+++ b/docs/ADDING_A_TOOL.md
@@ -0,0 +1,158 @@
+# Adding a tool to Ghostline
+
+Most contributions add a new tool to one of the existing modules. The
+process is intentionally short — a single function, a single menu line,
+optionally a few lines in `install.sh`.
+
+This document walks through the recipe step by step.
+
+---
+
+## Pick a module
+
+| Module | Use case |
+|------------------------------|-------------------------------------------------------|
+| `lib/modules/passive.sh` | Runs without credentials |
+| `lib/modules/active.sh` | Requires a domain user / password |
+| `lib/modules/special.sh` | Workflows, vuln scans, post-exploitation |
+
+If your tool doesn't fit any of the above, open an issue first so we can
+agree on whether to create a new module.
+
+---
+
+## Anatomy of a module function
+
+Every module function follows the same five-line shape:
+
+```bash
+_run_() {
+ require_target
+ [require_domain] # if needed
+ [require_credentials] # if needed
+ ensure_command "" "" || return 0
+ ensure_output_dir
+
+ log_step "Running ..."
+ "${GHOSTLINE_TARGET}" ... \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+```
+
+That's it. The framework handles colors, prompting, missing-tool
+warnings and output directory creation.
+
+If the binary may be packaged under several names (for example
+`crackmapexec` vs. `cme` vs. `nxc`), use `resolve_command` instead of
+`ensure_command`:
+
+```bash
+local cme
+if ! cme=$(resolve_command "crackmapexec" "cme" "nxc"); then
+ log_warn "Neither crackmapexec, cme nor nxc is installed."
+ log_info "Hint: pipx install netexec"
+ return 0
+fi
+"$cme" smb "${GHOSTLINE_TARGET}" -u "${GHOSTLINE_USERNAME}" ...
+```
+
+---
+
+## Wire it into the menu
+
+Two edits in the same module file:
+
+### 1. Update `generate__menu` in `lib/ui.sh`
+
+Add one line in the `menu_lines` array for the new entry:
+
+```bash
+"${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[6]${RESET} My Cool Tool"
+```
+
+### 2. Update `handle__menu` in `lib/modules/.sh`
+
+Add the matching case:
+
+```bash
+case "$choice" in
+ 1) passive_run_nmap ;;
+ 2) passive_run_enum4linux ;;
+ ...
+ 6) passive_run_my_cool_tool ;;
+ 0) return ;;
+esac
+```
+
+---
+
+## Update `install.sh` if the tool needs installing
+
+If the tool ships in the standard apt repos:
+
+```bash
+install_my_cool_tool() {
+ log_step "Installing my-cool-tool..."
+ apt_install my-cool-tool
+ log_success "my-cool-tool installed"
+}
+```
+
+If it's a pipx package:
+
+```bash
+install_my_cool_tool() {
+ log_step "Installing my-cool-tool..."
+ pipx_install my-cool-tool
+ log_success "my-cool-tool installed"
+}
+```
+
+If it's a GitHub project that needs cloning + symlink:
+
+```bash
+install_my_cool_tool() {
+ log_step "Installing my-cool-tool..."
+ local dest="${GHOSTLINE_TOOLS_DIR}/my-cool-tool"
+ clone_or_pull "https://github.com/owner/my-cool-tool.git" "$dest"
+ install_pip_requirements "$dest"
+ chmod +x "${dest}/main.py"
+ ln -sf "${dest}/main.py" /usr/local/bin/my-cool-tool
+ log_success "my-cool-tool installed"
+}
+```
+
+Then add the function to the `main` block at the bottom of `install.sh`.
+
+---
+
+## Checklist before opening the PR
+
+- [ ] The new module function follows the five-line shape above.
+- [ ] Tool presence is checked with `ensure_command` or `resolve_command`.
+- [ ] User input goes through `prompt_value` / `prompt_password`,
+ not raw `read`.
+- [ ] Logs go through `log_*`, not `echo -e ${RED}...${RESET}`.
+- [ ] Every variable expansion is quoted (`"${var}"`, not `$var`).
+- [ ] `bash -n` passes locally on the changed `.sh` files.
+- [ ] The menu line and the case in `handle__menu` are updated
+ in lockstep.
+- [ ] If the tool needs installing, `install.sh` is updated.
+- [ ] The `README.md` tool list is updated.
+
+---
+
+## Don't / Do
+
+| Don't | Do |
+|--------------------------------------------------------|-------------------------------------------------------------|
+| `echo -e "${RED}Running...${RESET}"` | `log_step "Running..."` |
+| `read -p "Target: " TARGET` | `target=$(prompt_value "Target")` |
+| `command -v foo \|\| { echo missing; return; }` | `ensure_command "foo" "apt install foo" \|\| return 0` |
+| `IFS=$'\n' read -r -d '' -a arr <<<"$STR"` | `mapfile -t arr <<<"$STR"` |
+| `cd /opt/foo && do_stuff && cd -` | `(cd /opt/foo \|\| exit 1; do_stuff)` |
+| `arr=( $(find . -name '*.txt') )` | `mapfile -t arr < <(find . -name '*.txt')` |
+| `$CMD --flag $TARGET` | `"$CMD" --flag "${GHOSTLINE_TARGET}"` |
+| French comments / log messages / docs | English everywhere |
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..5abfd2f
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,158 @@
+# Architecture
+
+Ghostline is a bash wrapper around ten-plus Active Directory enumeration
+tools. The goal is to expose a single, predictable menu instead of
+asking the operator to remember every flag of every tool.
+
+This document explains the layout of the codebase so that a new
+contributor can find the right file in seconds.
+
+---
+
+## File layout
+
+```
+ghostline.sh Entry point. ~50 lines.
+install.sh Installer for Debian / Ubuntu / Kali.
+lib/
+├── core.sh Colors (TTY-aware), palette, global state.
+├── ui.sh ASCII art, menus, banner rendering.
+├── installer.sh Logging, prompting and install primitives.
+└── modules/
+ ├── config.sh Target / domain / credentials / output config.
+ ├── passive.sh Unauthenticated reconnaissance.
+ ├── active.sh Authenticated enumeration.
+ └── special.sh Automated workflow, vuln scans, secrets dump.
+.github/
+├── workflows/ci.yml shellcheck + bash -n + smoke test.
+├── ISSUE_TEMPLATE/ Structured issue forms.
+└── PULL_REQUEST_TEMPLATE.md
+docs/
+├── ARCHITECTURE.md This document.
+└── ADDING_A_TOOL.md How to plug in a new tool.
+```
+
+---
+
+## Boot sequence
+
+```
+./ghostline.sh
+ │
+ ├── set -uo pipefail (strict-ish: -e omitted on purpose)
+ ├── SCRIPT_DIR=
+ │
+ ├── source lib/core.sh # palette + globals
+ ├── source lib/installer.sh # logging + prompts + helpers
+ ├── source lib/ui.sh # ascii art + menus
+ ├── source lib/modules/config.sh
+ ├── source lib/modules/passive.sh
+ ├── source lib/modules/active.sh
+ ├── source lib/modules/special.sh
+ │
+ └── main_loop
+ ├── display_title_middle_screen
+ ├── while true:
+ │ display_banner_with_menu "main"
+ │ read choice
+ │ case → handle_{config,passive,active,special}_menu
+ └── 0 → exit 0
+```
+
+`install.sh` follows the same source chain but only loads `core.sh` and
+`installer.sh` (it does not need the UI or modules).
+
+---
+
+## Why no `set -e`
+
+The interactive `ghostline.sh` deliberately uses `set -uo pipefail` and
+**not** `set -e`. Inside a menu loop, any tool that returns a non-zero
+exit code (for example `nmap` against an offline host, or `enum4linux-ng`
+on a closed SMB port) would otherwise kill the entire toolkit.
+
+`install.sh` is non-interactive and uses the full `set -euo pipefail`.
+
+---
+
+## Color handling
+
+`lib/core.sh` defines the palette. Colors are emitted **only** when
+stdout is a TTY (`[[ -t 1 ]]`) and `tput` is available. When piped or
+redirected, every color variable becomes the empty string so the output
+stays clean.
+
+---
+
+## Global state
+
+All shared runtime state lives in five variables defined in
+`lib/core.sh`:
+
+| Variable | Set by | Read by |
+|-----------------------------|---------------------------------------|-------------------------------------------|
+| `GHOSTLINE_TARGET` | `config_set_target`, `require_target` | All passive/active/special modules |
+| `GHOSTLINE_DOMAIN` | `config_set_domain`, `require_domain` | passive (ldap, dns), active, special |
+| `GHOSTLINE_USERNAME` | `config_set_credentials` | active modules, secrets dump |
+| `GHOSTLINE_PASSWORD` | `config_set_credentials` | active modules, secrets dump |
+| `GHOSTLINE_OUTPUT_DIR` | `config_set_output_dir` | every module that writes to disk |
+
+Modules never touch globals owned by another module.
+
+---
+
+## Public helpers (`lib/installer.sh`)
+
+| Helper | Purpose |
+|------------------------------|----------------------------------------------------|
+| `log_step / log_info / log_warn / log_error / log_success` | Color-coded logging |
+| `prompt_value LABEL [DEFAULT]` | Free-form input with optional default |
+| `prompt_password LABEL` | Silent password prompt |
+| `prompt_yesno LABEL [DEFAULT]` | y/N prompt that returns 0/1 |
+| `press_enter_to_continue` | Pause and wait for Enter |
+| `ensure_command CMD [HINT]` | Warn if `CMD` is missing, return 1 |
+| `resolve_command CMD...` | Echo first available command from a list |
+| `clone_or_pull URL DEST` | Git clone or fast-forward pull |
+| `pip_install PKG` | Best-effort pip install with PEP 668 fallbacks |
+| `pipx_install PKG` | Pipx install with `--force` and warning filter |
+| `apt_install PKG...` | Wrapper around `apt install -y` |
+| `require_root` | Exit if EUID != 0 |
+
+---
+
+## Public helpers (`lib/modules/config.sh`)
+
+| Helper | Purpose |
+|------------------------------|----------------------------------------------------|
+| `require_target` | Prompt if `GHOSTLINE_TARGET` is empty |
+| `require_domain` | Prompt if `GHOSTLINE_DOMAIN` is empty |
+| `require_credentials` | Prompt if username/password are empty |
+| `ensure_output_dir` | `mkdir -p "${GHOSTLINE_OUTPUT_DIR}"` |
+
+Every module function follows the same shape:
+
+```bash
+passive_run_() {
+ require_target
+ [require_domain | require_credentials]
+ ensure_command "" "" || return 0
+ ensure_output_dir
+ log_step "..."
+ ... | tee "${GHOSTLINE_OUTPUT_DIR}/"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+```
+
+---
+
+## CI
+
+Three jobs run on every push and PR:
+
+1. `shellcheck` — `severity: warning`, with
+ `SHELLCHECK_OPTS: -e SC1091 -e SC2034 -e SC2154`.
+2. `bash -n` — syntax check on every `.sh` in the tree.
+3. Smoke test — sources the full `lib/` chain and asserts that every
+ public function from `core.sh`, `installer.sh`, `ui.sh` and the four
+ modules is defined.
diff --git a/ghostline.sh b/ghostline.sh
old mode 100644
new mode 100755
index c6ae106..a6fabea
--- a/ghostline.sh
+++ b/ghostline.sh
@@ -1,681 +1,53 @@
-#!/bin/bash
+#!/usr/bin/env bash
+# Ghostline — Active Directory Enumeration Toolkit.
+# Entry point: load the library, then drive the interactive menu loop.
+
+set -uo pipefail
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+readonly SCRIPT_DIR
+
+# shellcheck source=lib/core.sh
+source "${SCRIPT_DIR}/lib/core.sh"
+# shellcheck source=lib/installer.sh
+source "${SCRIPT_DIR}/lib/installer.sh"
+# shellcheck source=lib/ui.sh
+source "${SCRIPT_DIR}/lib/ui.sh"
+# shellcheck source=lib/modules/config.sh
+source "${SCRIPT_DIR}/lib/modules/config.sh"
+# shellcheck source=lib/modules/passive.sh
+source "${SCRIPT_DIR}/lib/modules/passive.sh"
+# shellcheck source=lib/modules/active.sh
+source "${SCRIPT_DIR}/lib/modules/active.sh"
+# shellcheck source=lib/modules/special.sh
+source "${SCRIPT_DIR}/lib/modules/special.sh"
-################################################################################
-# GhostLine - Active Directory Enumeration Toolkit
-# Interactive toolkit for Active Directory enumeration
-# Author: Melvin PETIT
-################################################################################
-
-# ============================================================================
-# COLOR DEFINITIONS
-# ============================================================================
-readonly RESET="$(tput sgr0)"
-readonly BOLD="$(tput bold)"
-readonly DIM="$(tput dim)"
-
-readonly RED="$(tput setaf 1)"
-readonly GREEN="$(tput setaf 2)"
-readonly MAGENTA="$(tput setaf 5)"
-
-readonly BRIGHT_RED="$(tput setaf 9)"
-readonly BRIGHT_GREEN="$(tput setaf 10)"
-readonly BRIGHT_MAGENTA="$(tput setaf 13)"
-
-# ============================================================================
-# ASCII ART BANNER
-# ============================================================================
-readonly ASCII_ART=$(cat <<'ASCII'
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⢖⣠⣄⡀⠀⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣾⣿⣿⠎⠀⠀⠹⡀⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⣿⣿⣿⣿⢿⣤⠴⠒⢦⡇⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⠿⠛⠁⠸⡇⠀⠀⠀⡇⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣿⣿⣿⠿⠋⠁⠀⠀⠀⠀⣧⠀⠀⠀⡇⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⠂⠾⠿⣿⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⢠⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣷⣤⡀⠈⠉⠑⠒⠤⢀⡀⠀⠀⠀⠀⣿⠀⠀⠀⣼⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠟⠿⠿⢿⣿⣿⣆⠀⠀⠀⠀⠀⠈⠑⢄⠀⠀⣿⠀⠀⠀⡏⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⡏⡄⠀⠀⠀⠈⠻⣿⣧⠀⠀⠀⠀⠀⠀⠀⢳⠀⣿⠀⠀⠀⡇⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⡀⣷⠄⠀⠀⠀⠀⠙⢿⣇⡀⠀⠀⠀⠀⠀⠀⣷⡇⠀⠀⢸⡇⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⡿⠐⣁⣀⣀⢀⣾⣤⢤⣶⣿⣿⣦⡀⠀⠀⠀⠀⢸⠇⠀⠀⡾⠀⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣇⣾⣿⣿⣿⡇⠀⠻⣽⣿⣿⣿⡿⠿⣦⠀⠀⠀⡟⠀⠀⢰⠇⠀⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⡟⣌⡻⠛⠁⢰⠸⡔⣤⣉⡩⠐⠁⠀⢸⡇⠀⢰⠃⠀⠀⡎⠀⠀⠀
-⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣇⠀⠉⠀⠀⠀⠀⠀⠈⠙⠻⣶⢶⣶⣾⠇⢀⡏⠀⠀⠰⢣⠀⠀⠀
-⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⢋⣿⣿⣿⣿⣿⣿⣿⣷⠀⠤⠒⠒⠓⠘⠀⢴⢏⡟⠈⣿⠀⡞⠀⠀⢀⡇⠀⠣⡀⠀
-⠀⠀⠀⠀⠀⠀⣼⣿⡿⠁⣸⣿⣿⣿⣿⣿⣿⣿⣿⠀⡰⠞⠛⠛⠛⠳⠶⠿⠁⣰⣿⣼⠃⠀⠀⡼⢿⣶⡀⡇⠀
-⠀⠀⠀⠀⠀⣼⣿⠟⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣿⠟⠋⠉⠉⠁⠀⠀⢠⣿⣿⠃⠀⠀⠰⠁⠘⢿⠔⢀⣠
-⠀⠀⠀⠀⣰⣿⡟⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣏⠻⠖⠂⠈⠒⠂⢀⣠⣿⣿⠏⠀⠀⢀⣧⣶⡶⢖⣿⠇⡿
-⠀⠀⠀⢠⣿⡟⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣿⣏⠩⠭⣼⣿⣿⠏⠀⠀⠀⡼⠛⢉⣴⣿⠏⣸⡇
-⠀⠀⢀⣾⡟⠀⠀⠀⠀⠀⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⠏⠀⠀⠀⡼⢁⣴⣿⡿⠁⣰⣿⠁
-⠀⠀⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⠿⠿⠛⢛⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⣰⣿⣿⡿⠋⠀⣰⣿⡟⢠
-⠀⠀⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⢠⣿⡿⠛⠁⠀⣼⣿⣿⣧⣿
-⠀⢸⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⡿⠟⢻⠐⠉⠑⠤⣀⢠⣿⣿⠀⠀⢀⣾⣿⣿⣿⣿⣿
-⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⢱⠹⠉⢉⠉⠓⠶⠤⠍⠛⢻⡄⣠⣿⣿⣿⣿⣿⣿⣿
-⢸⠀⠀⠀⠀⠀⠀⢀⣤⣴⡖⠤⣀⠀⠀⠀⣼⣿⣿⣿⠉⠉⠑⠛⠛⠛⠳⢄⣀⠈⢹⠤⣿⣿⣿⣿⣿⣿⣿⣿⣿
-ASCII
-)
-
-# ============================================================================
-# CONFIGURATION
-# ============================================================================
-TARGET=""
-DOMAIN=""
-USERNAME=""
-PASSWORD=""
-OUTPUT_DIR="ad_enum_$(date +%Y%m%d_%H%M%S)"
-
-# ============================================================================
-# MAIN MENU
-# ============================================================================
-generate_main_menu() {
- local -a menu_lines=(
- "${BRIGHT_RED} ▄██████▄ ▄█ █▄ ▄██████▄ ▄████████ ███ ▄█ ▄█ ███▄▄▄▄ ▄████████${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ▀█████████▄ ███ ███ ███▀▀▀██▄ ███ ███${RESET}"
- "${BRIGHT_RED} ███ █▀ ███ ███ ███ ███ ███ █▀ ▀███▀▀██ ███ ███▌ ███ ███ ███ █▀${RESET}"
- "${BRIGHT_RED} ▄███ ▄███▄▄▄▄███▄▄ ███ ███ ███ ███ ▀ ███ ███▌ ███ ███ ▄███▄▄▄${RESET}"
- "${BRIGHT_RED}▀▀███ ████▄ ▀▀███▀▀▀▀███▀ ███ ███ ▀███████████ ███ ███ ███▌ ███ ███ ▀▀███▀▀▀${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ▄█ ███ ███ ███▌ ▄ ███ ███ ███ ███ ███${RESET}"
- "${BRIGHT_RED} ████████▀ ███ █▀ ▀██████▀ ▄████████▀ ▄████▀ █████▄▄██ █▀ ▀█ █▀ ██████████${RESET}"
- " "
- "${BRIGHT_RED}█"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_MAGENTA}Configuration:${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} Target: ${BRIGHT_MAGENTA}${TARGET:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} Domain: ${BRIGHT_MAGENTA}${DOMAIN:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} User: ${BRIGHT_MAGENTA}${USERNAME:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_MAGENTA}Hunt for Active Directory intelligence:${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Configuration Menu"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} Passive Enumeration"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} Active Enumeration"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} Special Actions"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Exit"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- )
- printf '%s\n' "${menu_lines[@]}"
-}
-
-# ============================================================================
-# CONFIGURATION SUBMENU
-# ============================================================================
-generate_config_menu() {
- local -a menu_lines=(
- "${BRIGHT_RED} ▄████████ ▄██████▄ ███▄▄▄▄ ▄████████ ▄█ ▄██████▄ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ███ ███ ███ ${RESET}"
- "${BRIGHT_RED} ███ █▀ ███ ███ ███ ███ ███ █▀ ███▌ ███ █▀ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ▄███▄▄▄ ███▌ ▄███ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ▀▀███▀▀▀ ███▌ ▀▀███ ████▄ ${RESET}"
- "${BRIGHT_RED} ███ █▄ ███ ███ ███ ███ ███ ███ ███ ███ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ${RESET}"
- "${BRIGHT_RED} ████████▀ ▀██████▀ ▀█ █▀ ███ █▀ ████████▀ ${RESET}"
- " "
- "${BRIGHT_MAGENTA}Configure your hunting parameters${RESET}"
- " "
- "${BOLD}${BRIGHT_RED}█${RESET} Current Configuration:"
- "${BOLD}${BRIGHT_RED}█${RESET} Target: ${BRIGHT_MAGENTA}${TARGET:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} Domain: ${BRIGHT_MAGENTA}${DOMAIN:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} User: ${BRIGHT_MAGENTA}${USERNAME:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} Output: ${BRIGHT_MAGENTA}${OUTPUT_DIR}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Set Target (IP/Hostname)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} Set Domain"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} Set Credentials"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} Set Output Directory"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- )
- printf '%s\n' "${menu_lines[@]}"
-}
-
-# ============================================================================
-# PASSIVE ENUMERATION SUBMENU
-# ============================================================================
-generate_passive_menu() {
- local -a menu_lines=(
- "${BRIGHT_RED} ▄███████▄ ▄████████ ▄████████ ▄████████ ▄█ ███ ▄█ █▄ ▄████████${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ▀█████████▄ ███ ███ ███ ███${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ █▀ ███ █▀ ███▌ ▀███▀▀██ ███ ███ ███ █▀ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███▌ ███ ▀ ███ ███ ▄███▄▄▄ ${RESET}"
- "${BRIGHT_RED}▀█████████▀ ▀███████████ ▀███████████ ▀███████████ ███▌ ███ ███ ███ ▀▀███▀▀▀ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ▄█ ███ ▄█ ███ ███ ███ ███ ███ ███ ███${RESET}"
- "${BRIGHT_RED} ▄████▀ ███ █▀ ▄████████▀ ▄████████▀ █▀ ▄████▀ ▀██████▀ ██████████${RESET}"
- " "
- "${BRIGHT_MAGENTA}Silent reconnaissance without credentials${RESET}"
- " "
- "${BOLD}${BRIGHT_RED}█${RESET} Target: ${BRIGHT_MAGENTA}${TARGET:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Nmap Scan (Port Discovery)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} Enum4linux-ng (SMB Enumeration)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} RPC Client (Null Session)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} LDAP Search (Anonymous)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[5]${RESET} DNS Enumeration"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
-
- )
- printf '%s\n' "${menu_lines[@]}"
-}
-
-# ============================================================================
-# ACTIVE ENUMERATION SUBMENU
-# ============================================================================
-generate_active_menu() {
- local -a menu_lines=(
- "${BRIGHT_RED} ▄████████ ▄████████ ███ ▄█ ███ ▄█ █▄ ▄████████${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ▀█████████▄ ███ ▀█████████▄ ███ ███ ███ ███${RESET}"
- "${BRIGHT_RED} ███ ███ ███ █▀ ▀███▀▀██ ███▌ ▀███▀▀██ ███ ███ ███ █▀ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ▀ ███▌ ███ ▀ ███ ███ ▄███▄▄▄ ${RESET}"
- "${BRIGHT_RED} ▀███████████ ███ ███ ███▌ ███ ███ ███ ▀▀███▀▀▀ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ █▄ ███ ███ ███ ███ ███ ███ █▄ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███${RESET}"
- "${BRIGHT_RED} ███ █▀ ████████▀ ▄████▀ █▀ ▄████▀ ▀██████▀ ██████████${RESET}"
- " "
- "${BRIGHT_MAGENTA}Authenticated enumeration with credentials${RESET}"
- " "
- "${BOLD}${BRIGHT_RED}█${RESET} Credentials: ${BRIGHT_MAGENTA}${USERNAME:-Not set}${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} BloodHound Collection"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} CrackMapExec (Full Enum)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} AD DNS Dump"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} GetNPUsers (ASREPRoast)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[5]${RESET} RID Enumeration"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- )
- printf '%s\n' "${menu_lines[@]}"
-}
-
-# ============================================================================
-# SPECIAL ACTIONS SUBMENU
-# ============================================================================
-generate_special_menu() {
- local -a menu_lines=(
- "${BRIGHT_RED} ▄████████ ▄███████▄ ▄████████ ▄████████ ▄█ ▄████████ ▄█ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ${RESET}"
- "${BRIGHT_RED} ███ █▀ ███ ███ ███ █▀ ███ █▀ ███▌ ███ ███ ███ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ▄███▄▄▄ ███ ███▌ ███ ███ ███ ${RESET}"
- "${BRIGHT_RED}▀███████████ ▀█████████▀ ▀▀███▀▀▀ ███ ███▌ ▀███████████ ███ ${RESET}"
- "${BRIGHT_RED} ███ ███ ███ █▄ ███ █▄ ███ ███ ███ ███ ${RESET}"
- "${BRIGHT_RED} ▄█ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ▄ ${RESET}"
- "${BRIGHT_RED} ▄████████▀ ▄████▀ ██████████ ████████▀ █▀ ███ █▀ █████▄▄██ ${RESET}"
- " "
- "${BRIGHT_MAGENTA}Advanced operations and automated workflows${RESET}"
- " "
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Auto Workflow (Full Scan)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} SMB Vulnerabilities Scan"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} Secrets Dump (Requires Admin)"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} View Results"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- "${BOLD}${BRIGHT_RED}█${RESET}"
- )
- printf '%s\n' "${menu_lines[@]}"
-}
-
-# ============================================================================
-# TITLE SCREEN
-# ============================================================================
-readonly INFO_PANEL=(
- "${BRIGHT_RED} ▄██████▄ ▄█ █▄ ▄██████▄ ▄████████ ███ ▄█ ▄█ ███▄▄▄▄ ▄████████${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ▀█████████▄ ███ ███ ███▀▀▀██▄ ███ ███${RESET}"
- "${BRIGHT_RED} ███ █▀ ███ ███ ███ ███ ███ █▀ ▀███▀▀██ ███ ███▌ ███ ███ ███ █▀${RESET}"
- "${BRIGHT_RED} ▄███ ▄███▄▄▄▄███▄▄ ███ ███ ███ ███ ▀ ███ ███▌ ███ ███ ▄███▄▄▄${RESET}"
- "${BRIGHT_RED}▀▀███ ████▄ ▀▀███▀▀▀▀███▀ ███ ███ ▀███████████ ███ ███ ███▌ ███ ███ ▀▀███▀▀▀${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄${RESET}"
- "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ▄█ ███ ███ ███▌ ▄ ███ ███ ███ ███ ███${RESET}"
- "${BRIGHT_RED} ████████▀ ███ █▀ ▀██████▀ ▄████████▀ ▄████▀ █████▄▄██ █▀ ▀█ █▀ ██████████${RESET}"
- ""
- "${BRIGHT_MAGENTA}${BOLD}Active Directory OSINT Toolkit${RESET}"
- "${DIM}by Melvin PETIT${RESET}"
-)
-
-display_title_middle_screen() {
- local cols rows
- cols=$(tput cols 2>/dev/null || echo 80)
- rows=$(tput lines 2>/dev/null || echo 24)
-
- local -a lines=( "${INFO_PANEL[@]}" )
- local h=${#lines[@]}
-
- strip_esc() {
- sed -E 's/\x1B\[[0-9;?]*[ -/]*[@-~]//g; s/\x1B\][^\a]*\a//g'
- }
-
- local max_w=0 raw visible_len line
- for line in "${lines[@]}"; do
- raw=$(printf "%s" "$line" | strip_esc)
- visible_len=${#raw}
- (( visible_len > max_w )) && max_w=$visible_len
- done
-
- local top=$(( (rows - h) / 2 ))
- (( top < 0 )) && top=0
- local left=$(( (cols - max_w) / 2 ))
- (( left < 0 )) && left=0
-
- printf "\033c"
- for ((i=0; i menu_count ? ascii_count : menu_count))
-
- local max_ascii_width=0
- for line in "${ascii_lines[@]}"; do
- ((${#line} > max_ascii_width)) && max_ascii_width=${#line}
- done
-
- local spacing=" "
-
- for ((i=0; i/dev/null
-
- echo -e "${BRIGHT_MAGENTA}► Phase 2: SMB Enumeration${RESET}"
- enum4linux-ng -A "$TARGET" > "$OUTPUT_DIR/enum4linux.txt" 2>/dev/null
-
- echo -e "${BRIGHT_MAGENTA}► Phase 3: RPC Enumeration${RESET}"
- { echo "enumdomusers"; echo "exit"; } | rpcclient -U "" "$TARGET" -N > "$OUTPUT_DIR/rpc.txt" 2>/dev/null
-
- [ -n "$DOMAIN" ] && {
- echo -e "${BRIGHT_MAGENTA}► Phase 4: LDAP Query${RESET}"
- BASE_DN=$(echo "$DOMAIN" | sed 's/\./,dc=/g' | sed 's/^/dc=/')
- ldapsearch -x -H ldap://"$TARGET" -b "$BASE_DN" "(objectclass=user)" > "$OUTPUT_DIR/ldap.txt" 2>/dev/null
- }
-
- echo ""
- echo -e "${BRIGHT_GREEN}✓ Workflow complete! All results in ${OUTPUT_DIR}/${RESET}"
- read -r
- ;;
- 2)
- echo -e "\n${BRIGHT_MAGENTA}SMB Vulnerabilities${RESET}"
- check_target
- mkdir -p "$OUTPUT_DIR"
- echo -e "${BRIGHT_MAGENTA}Scanning for SMB vulnerabilities...${RESET}"
- nmap -p 445 --script smb-vuln* "$TARGET" -oA "$OUTPUT_DIR/smb_vulns"
- echo -e "${BRIGHT_GREEN}✓ Vulnerability scan complete${RESET}"
- read -r
- ;;
- 3)
- echo -e "\n${BRIGHT_MAGENTA}Secrets Dump${RESET}"
- check_target
- check_domain
- check_credentials
- mkdir -p "$OUTPUT_DIR"
- SECRETSDUMP=$(command -v secretsdump.py || command -v impacket-secretsdump)
- echo -e "${BRIGHT_RED}${BOLD}⚠ This requires elevated privileges!${RESET}"
- echo -e "${BRIGHT_MAGENTA}Dumping secrets...${RESET}"
- $SECRETSDUMP "$DOMAIN/$USERNAME:$PASSWORD@$TARGET" | tee "$OUTPUT_DIR/secrets.txt"
- echo -e "${BRIGHT_GREEN}✓ Secrets saved${RESET}"
- read -r
- ;;
- 4)
- echo -e "\n${BRIGHT_MAGENTA}Results Viewer${RESET}"
- if [ ! -d "$OUTPUT_DIR" ]; then
- echo -e "${BRIGHT_RED}No results directory found${RESET}"
- else
- echo -e "${BRIGHT_MAGENTA}Files in $OUTPUT_DIR:${RESET}"
- ls -lh "$OUTPUT_DIR"
- fi
- read -r
- ;;
- 0) return ;;
- *)
- echo -e "\n${BRIGHT_RED}Invalid choice!${RESET}"
- sleep 1
- ;;
- esac
- done
-}
-
-# ============================================================================
-# MAIN MENU HANDLER
-# ============================================================================
-handle_main_menu() {
- local choice=$1
-
- case $choice in
- 1) handle_config_menu ;;
- 2) handle_passive_menu ;;
- 3) handle_active_menu ;;
- 4) handle_special_menu ;;
- 0)
- echo -e "\n${BRIGHT_MAGENTA}Exiting GhostLine...${RESET}"
- exit 0
- ;;
- *)
- echo -e "\n${BRIGHT_RED}Invalid choice!${RESET}"
- sleep 1
- ;;
- esac
-}
-
-# ============================================================================
-# MAIN LOOP
-# ============================================================================
-main_loop() {
- display_title_middle_screen
- sleep 2
-
- while true; do
- clear
- display_banner_with_menu "main"
- echo -ne " ${BOLD}${BRIGHT_RED}▪ Choose your line, sir : ${RESET}"
- read -r choice
-
- handle_main_menu "$choice"
- done
-}
-
-main_loop
\ No newline at end of file
+main_loop "$@"
diff --git a/install.sh b/install.sh
old mode 100644
new mode 100755
index 1689859..7eb368a
--- a/install.sh
+++ b/install.sh
@@ -1,313 +1,238 @@
-#!/bin/bash
-
-################################################################################
-# Script d'installation des outils d'énumération Active Directory
-# Version simplifiée - Installation uniquement
-################################################################################
-
-set -e
-
-# Couleurs
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m'
-
-print_status() { echo -e "${BLUE}[*]${NC} $1"; }
-print_success() { echo -e "${GREEN}[+]${NC} $1"; }
-print_error() { echo -e "${RED}[-]${NC} $1"; }
-print_warning() { echo -e "${YELLOW}[!]${NC} $1"; }
-
-################################################################################
-# VÉRIFICATION ROOT
-################################################################################
-
-if [[ $EUID -ne 0 ]]; then
- print_error "Ce script doit être exécuté en tant que root (sudo)"
- exit 1
-fi
-
-################################################################################
-# CORRECTION DES DÉPÔTS PROBLÉMATIQUES
-################################################################################
-
-print_status "Correction des dépôts problématiques..."
-
-# Créer le dossier de sauvegarde
-mkdir -p /etc/apt/sources.list.d/disabled 2>/dev/null
-
-# Désactiver WineHQ
-if ls /etc/apt/sources.list.d/*winehq* 1> /dev/null 2>&1; then
- mv /etc/apt/sources.list.d/*winehq* /etc/apt/sources.list.d/disabled/ 2>/dev/null || true
-fi
-
-# Désactiver Docker avec version incorrecte
-if grep -r "zara" /etc/apt/sources.list.d/ 2>/dev/null; then
- grep -rl "zara" /etc/apt/sources.list.d/ | while read file; do
- mv "$file" /etc/apt/sources.list.d/disabled/ 2>/dev/null || true
- done
-fi
-
-print_success "Dépôts corrigés"
-
-################################################################################
-# MISE À JOUR DU SYSTÈME
-################################################################################
-
-print_status "Mise à jour du système..."
-apt update -y 2>&1 | grep -v "NO_PUBKEY\|pas signé" || true
-print_success "Système mis à jour"
-
-################################################################################
-# INSTALLATION DES DÉPENDANCES DE BASE
-################################################################################
-
-print_status "Installation des dépendances de base..."
-
-# Recherche du paquet LDAP disponible
-LDAP_PKG=$(apt-cache search "ldap" 2>/dev/null | grep -E "ldap.*utils|openldap.*client" | head -1 | awk '{print $1}')
-[ -z "$LDAP_PKG" ] && LDAP_PKG="ldap-utils"
-
-apt install -y \
- git \
- python3 \
- python3-pip \
- python3-venv \
- python3-full \
- pipx \
- samba \
- samba-common-bin \
- smbclient \
- "${LDAP_PKG}" \
- nmap \
- dnsrecon \
- dnsenum \
- curl \
- wget \
- build-essential \
- libsasl2-dev \
- libldap2-dev \
- libssl-dev 2>&1 | grep -v "WARNING" || true
-
-print_success "Dépendances de base installées"
-
-# Configuration pipx
-export PATH="$PATH:/root/.local/bin:$HOME/.local/bin"
-pipx ensurepath 2>/dev/null || true
-
-################################################################################
-# FONCTION D'INSTALLATION PYTHON
-################################################################################
-
-pip_install() {
- local package=$1
- python3 -m pip install --user --ignore-installed "$package" 2>/dev/null || \
- python3 -m pip install --break-system-packages --ignore-installed "$package" 2>/dev/null || \
- python3 -m pip install --user "$package" 2>/dev/null || \
- python3 -m pip install --break-system-packages "$package" 2>/dev/null
-}
-
-################################################################################
-# 1. ENUM4LINUX-NG
-################################################################################
-
-print_status "Installation de enum4linux-ng..."
-
-cd /opt
-if [ -d "enum4linux-ng" ]; then
- cd enum4linux-ng && git pull
-else
- git clone https://github.com/cddmp/enum4linux-ng.git
- cd enum4linux-ng
-fi
-
-if [ -f "requirements.txt" ]; then
- while IFS= read -r pkg; do
- [ -z "$pkg" ] || [[ "$pkg" =~ ^# ]] && continue
- pip_install "$pkg"
- done < requirements.txt
-fi
-
-chmod +x enum4linux-ng.py
-ln -sf /opt/enum4linux-ng/enum4linux-ng.py /usr/local/bin/enum4linux-ng
-
-print_success "enum4linux-ng installé"
-
-################################################################################
-# 2. CRACKMAPEXEC
-################################################################################
-
-print_status "Installation de CrackMapExec..."
-
-if apt install -y crackmapexec 2>/dev/null; then
- print_success "CrackMapExec installé via apt"
-else
- pipx install crackmapexec --force 2>&1 | grep -v "WARNING" || true
- print_success "CrackMapExec installé via pipx"
-fi
-
-################################################################################
-# 3. ADIDNSDUMP
-################################################################################
-
-print_status "Installation de adidnsdump..."
+#!/usr/bin/env bash
+# install.sh — Install every dependency required by Ghostline.
+# Designed for Debian/Ubuntu/Kali. Run with sudo.
-cd /opt
-if [ -d "adidnsdump" ]; then
- cd adidnsdump && git pull
-else
- git clone https://github.com/dirkjanm/adidnsdump.git
- cd adidnsdump
-fi
+set -euo pipefail
-if [ -f "requirements.txt" ]; then
- while IFS= read -r pkg; do
- [ -z "$pkg" ] || [[ "$pkg" =~ ^# ]] && continue
- pip_install "$pkg"
- done < requirements.txt
-fi
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+readonly SCRIPT_DIR
-pip_install "."
+# shellcheck source=lib/core.sh
+source "${SCRIPT_DIR}/lib/core.sh"
+# shellcheck source=lib/installer.sh
+source "${SCRIPT_DIR}/lib/installer.sh"
-print_success "adidnsdump installé"
+require_root
-################################################################################
-# 4. BLOODHOUND.PY
-################################################################################
+# ---------------------------------------------------------------------------
+# Disable known broken apt repositories.
+# ---------------------------------------------------------------------------
+disable_broken_repos() {
+ log_step "Cleaning up broken apt repositories..."
+ mkdir -p /etc/apt/sources.list.d/disabled
-print_status "Installation de BloodHound.py..."
-
-pipx install bloodhound --force 2>&1 | grep -v "WARNING" || pip_install "bloodhound"
-
-print_success "BloodHound.py installé"
-
-################################################################################
-# 5. RIDENUM
-################################################################################
-
-print_status "Installation de ridenum..."
-
-cd /opt
-if [ -d "ridenum" ]; then
- cd ridenum && git pull
-else
- git clone https://github.com/trustedsec/ridenum.git
-fi
+ if ls /etc/apt/sources.list.d/*winehq* >/dev/null 2>&1; then
+ mv /etc/apt/sources.list.d/*winehq* /etc/apt/sources.list.d/disabled/ \
+ 2>/dev/null || true
+ fi
-chmod +x /opt/ridenum/ridenum.py
-ln -sf /opt/ridenum/ridenum.py /usr/local/bin/ridenum
+ if grep -r "zara" /etc/apt/sources.list.d/ >/dev/null 2>&1; then
+ while IFS= read -r file; do
+ mv "$file" /etc/apt/sources.list.d/disabled/ 2>/dev/null || true
+ done < <(grep -rl "zara" /etc/apt/sources.list.d/)
+ fi
-print_success "ridenum installé"
+ log_success "Repositories cleaned"
+}
-################################################################################
-# 6. IMPACKET
-################################################################################
+# ---------------------------------------------------------------------------
+# Resolve an ldap-utils-equivalent package available on the current distro.
+# ---------------------------------------------------------------------------
+resolve_ldap_package() {
+ local pkg
+ pkg=$(apt-cache search "ldap" 2>/dev/null \
+ | grep -E "ldap.*utils|openldap.*client" \
+ | head -1 \
+ | awk '{print $1}')
+ if [[ -z "$pkg" ]]; then
+ pkg="ldap-utils"
+ fi
+ printf '%s' "$pkg"
+}
-print_status "Installation de Impacket..."
+install_base_dependencies() {
+ log_step "Installing base dependencies..."
+
+ local ldap_pkg
+ ldap_pkg=$(resolve_ldap_package)
+
+ apt update -y 2>&1 | grep -v "NO_PUBKEY\|not signed" || true
+
+ apt_install \
+ git \
+ python3 \
+ python3-pip \
+ python3-venv \
+ python3-full \
+ pipx \
+ samba \
+ samba-common-bin \
+ smbclient \
+ "${ldap_pkg}" \
+ nmap \
+ dnsrecon \
+ dnsenum \
+ curl \
+ wget \
+ build-essential \
+ libsasl2-dev \
+ libldap2-dev \
+ libssl-dev 2>&1 | grep -v "WARNING" || true
+
+ log_success "Base dependencies installed"
+
+ export PATH="${PATH}:/root/.local/bin:${HOME}/.local/bin"
+ pipx ensurepath 2>/dev/null || true
+}
-pipx install impacket --force 2>&1 | grep -v "WARNING" || pip_install "impacket"
+install_enum4linux_ng() {
+ log_step "Installing enum4linux-ng..."
+ local dest="${GHOSTLINE_TOOLS_DIR}/enum4linux-ng"
+ clone_or_pull "https://github.com/cddmp/enum4linux-ng.git" "$dest"
+ install_pip_requirements "$dest"
+ chmod +x "${dest}/enum4linux-ng.py"
+ ln -sf "${dest}/enum4linux-ng.py" /usr/local/bin/enum4linux-ng
+ log_success "enum4linux-ng installed"
+}
-print_success "Impacket installé"
+install_crackmapexec() {
+ log_step "Installing CrackMapExec..."
+ if apt_install crackmapexec 2>/dev/null; then
+ log_success "CrackMapExec installed via apt"
+ else
+ pipx_install crackmapexec
+ log_success "CrackMapExec installed via pipx"
+ fi
+}
-################################################################################
-# 7. KERBRUTE
-################################################################################
+install_adidnsdump() {
+ log_step "Installing adidnsdump..."
+ local dest="${GHOSTLINE_TOOLS_DIR}/adidnsdump"
+ clone_or_pull "https://github.com/dirkjanm/adidnsdump.git" "$dest"
+ install_pip_requirements "$dest"
+ (cd "$dest" || exit 1; pip_install ".")
+ log_success "adidnsdump installed"
+}
-print_status "Installation de Kerbrute..."
-
-# Détecter l'architecture
-ARCH=$(uname -m)
-case "$ARCH" in
- x86_64)
- KERBRUTE_ARCH="amd64"
- ;;
- aarch64|arm64)
- KERBRUTE_ARCH="arm64"
- ;;
- armv7l)
- KERBRUTE_ARCH="arm"
- ;;
- *)
- print_warning "Architecture non supportée pour Kerbrute: $ARCH"
- KERBRUTE_ARCH="amd64"
- ;;
-esac
+install_bloodhound_py() {
+ log_step "Installing BloodHound.py..."
+ pipx_install bloodhound || pip_install bloodhound
+ log_success "BloodHound.py installed"
+}
-# Télécharger la dernière version
-KERBRUTE_VERSION="1.0.3"
-KERBRUTE_URL="https://github.com/ropnop/kerbrute/releases/download/v${KERBRUTE_VERSION}/kerbrute_linux_${KERBRUTE_ARCH}"
+install_ridenum() {
+ log_step "Installing ridenum..."
+ local dest="${GHOSTLINE_TOOLS_DIR}/ridenum"
+ clone_or_pull "https://github.com/trustedsec/ridenum.git" "$dest"
+ chmod +x "${dest}/ridenum.py"
+ ln -sf "${dest}/ridenum.py" /usr/local/bin/ridenum
+ log_success "ridenum installed"
+}
-cd /opt
-wget -q "$KERBRUTE_URL" -O kerbrute 2>/dev/null || curl -sL "$KERBRUTE_URL" -o kerbrute
-
-if [ -f "kerbrute" ]; then
- chmod +x kerbrute
- ln -sf /opt/kerbrute /usr/local/bin/kerbrute
- print_success "Kerbrute installé"
-else
- print_warning "Échec du téléchargement de Kerbrute"
-fi
+install_impacket() {
+ log_step "Installing Impacket..."
+ pipx_install impacket || pip_install impacket
+ log_success "Impacket installed"
+}
-################################################################################
-# 8. LDAPDOMAINDUMP
-################################################################################
+install_kerbrute() {
+ log_step "Installing Kerbrute..."
+ local arch kerbrute_arch
+ arch=$(uname -m)
+ case "$arch" in
+ x86_64) kerbrute_arch="amd64" ;;
+ aarch64|arm64) kerbrute_arch="arm64" ;;
+ armv7l) kerbrute_arch="arm" ;;
+ *)
+ log_warn "Architecture not supported for Kerbrute: ${arch}"
+ kerbrute_arch="amd64"
+ ;;
+ esac
+
+ local version="1.0.3"
+ local url="https://github.com/ropnop/kerbrute/releases/download/v${version}/kerbrute_linux_${kerbrute_arch}"
+ local dest="${GHOSTLINE_TOOLS_DIR}/kerbrute"
+
+ (cd "${GHOSTLINE_TOOLS_DIR}" || exit 1
+ wget -q "$url" -O kerbrute 2>/dev/null \
+ || curl -sL "$url" -o kerbrute)
+
+ if [[ -f "$dest" ]]; then
+ chmod +x "$dest"
+ ln -sf "$dest" /usr/local/bin/kerbrute
+ log_success "Kerbrute installed"
+ else
+ log_warn "Failed to download Kerbrute"
+ fi
+}
-print_status "Installation de ldapdomaindump..."
+install_ldapdomaindump() {
+ log_step "Installing ldapdomaindump..."
+ pip_install ldapdomaindump
+ log_success "ldapdomaindump installed"
+}
-pip_install "ldapdomaindump"
+configure_path() {
+ log_step "Configuring PATH..."
+ if ! grep -q ".local/bin" /root/.bashrc 2>/dev/null; then
+ echo 'export PATH="$PATH:$HOME/.local/bin:/root/.local/bin"' >> /root/.bashrc
+ fi
-print_success "ldapdomaindump installé"
+ if [[ -n "${SUDO_USER:-}" ]]; then
+ local user_home
+ user_home=$(getent passwd "$SUDO_USER" | cut -d: -f6)
+ if [[ -f "${user_home}/.bashrc" ]]; then
+ if ! grep -q ".local/bin" "${user_home}/.bashrc"; then
+ echo 'export PATH="$PATH:$HOME/.local/bin"' >> "${user_home}/.bashrc"
+ chown "${SUDO_USER}:${SUDO_USER}" "${user_home}/.bashrc"
+ fi
+ fi
+ fi
-################################################################################
-# CONFIGURATION FINALE
-################################################################################
+ export PATH="${PATH}:${HOME}/.local/bin:/root/.local/bin"
+}
-print_status "Configuration du PATH..."
+print_summary() {
+ echo ""
+ echo "========================================================================"
+ log_success "Installation complete."
+ echo "========================================================================"
+ echo ""
+ echo "Installed tools:"
+ printf " [1] enum4linux-ng -> %s\n" "/usr/local/bin/enum4linux-ng"
+ printf " [2] ldapsearch -> %s\n" "$(command -v ldapsearch 2>/dev/null || echo 'install manually')"
+ printf " [3] nmap + NSE -> %s\n" "$(command -v nmap 2>/dev/null || echo 'not found')"
+ printf " [4] rpcclient -> %s\n" "$(command -v rpcclient 2>/dev/null || echo 'not found')"
+ printf " [5] CrackMapExec -> %s\n" "$(command -v crackmapexec 2>/dev/null || command -v cme 2>/dev/null || echo "${HOME}/.local/bin/")"
+ printf " [6] adidnsdump -> %s\n" "$(command -v adidnsdump 2>/dev/null || echo "${HOME}/.local/bin/")"
+ printf " [7] BloodHound.py -> %s\n" "$(command -v bloodhound-python 2>/dev/null || echo "${HOME}/.local/bin/")"
+ printf " [8] ridenum -> %s\n" "/usr/local/bin/ridenum"
+ printf " [9] Impacket -> %s\n" "$(command -v secretsdump.py 2>/dev/null || command -v impacket-secretsdump 2>/dev/null || echo "${HOME}/.local/bin/")"
+ printf " [10] dnsrecon/dnsenum -> %s\n" "$(command -v dnsrecon 2>/dev/null || echo 'not found')"
+ printf " [11] Kerbrute -> %s\n" "/usr/local/bin/kerbrute"
+ printf " [12] ldapdomaindump -> %s\n" "$(command -v ldapdomaindump 2>/dev/null || echo "${HOME}/.local/bin/")"
+ echo " [13] GetUserSPNs.py -> (included in Impacket)"
+ echo ""
+ echo "IMPORTANT:"
+ echo " Reload your shell: source ~/.bashrc"
+ echo " Or restart your terminal."
+ echo ""
+ echo "========================================================================"
+}
-# Ajout au bashrc
-if ! grep -q ".local/bin" /root/.bashrc 2>/dev/null; then
- echo 'export PATH="$PATH:$HOME/.local/bin:/root/.local/bin"' >> /root/.bashrc
-fi
+main() {
+ disable_broken_repos
+ install_base_dependencies
+ install_enum4linux_ng
+ install_crackmapexec
+ install_adidnsdump
+ install_bloodhound_py
+ install_ridenum
+ install_impacket
+ install_kerbrute
+ install_ldapdomaindump
+ configure_path
+ print_summary
+}
-if [ -n "$SUDO_USER" ]; then
- USER_HOME=$(eval echo ~$SUDO_USER)
- if [ -f "$USER_HOME/.bashrc" ]; then
- if ! grep -q ".local/bin" "$USER_HOME/.bashrc"; then
- echo 'export PATH="$PATH:$HOME/.local/bin"' >> "$USER_HOME/.bashrc"
- chown $SUDO_USER:$SUDO_USER "$USER_HOME/.bashrc"
- fi
- fi
-fi
-
-export PATH="$PATH:$HOME/.local/bin:/root/.local/bin"
-
-################################################################################
-# RÉSUMÉ
-################################################################################
-
-echo ""
-echo "========================================================================"
-echo -e "${GREEN}✓ Installation terminée avec succès!${NC}"
-echo "========================================================================"
-echo ""
-echo "Outils installés:"
-echo " [1] enum4linux-ng -> /usr/local/bin/enum4linux-ng"
-echo " [2] ldapsearch -> $(which ldapsearch 2>/dev/null || echo 'À installer manuellement')"
-echo " [3] nmap + NSE -> $(which nmap 2>/dev/null || echo 'Non trouvé')"
-echo " [4] rpcclient -> $(which rpcclient 2>/dev/null || echo 'Non trouvé')"
-echo " [5] CrackMapExec -> $(which crackmapexec 2>/dev/null || which cme 2>/dev/null || echo '~/.local/bin/')"
-echo " [6] adidnsdump -> $(which adidnsdump 2>/dev/null || echo '~/.local/bin/')"
-echo " [7] BloodHound.py -> $(which bloodhound-python 2>/dev/null || echo '~/.local/bin/')"
-echo " [8] ridenum -> /usr/local/bin/ridenum"
-echo " [9] Impacket -> $(which secretsdump.py 2>/dev/null || which impacket-secretsdump 2>/dev/null || echo '~/.local/bin/')"
-echo " [10] dnsrecon/dnsenum -> $(which dnsrecon 2>/dev/null || echo 'Non trouvé')"
-echo " [11] Kerbrute -> /usr/local/bin/kerbrute"
-echo " [12] ldapdomaindump -> $(which ldapdomaindump 2>/dev/null || echo '~/.local/bin/')"
-echo " [13] GetUserSPNs.py -> (Included in Impacket)"
-echo ""
-echo "IMPORTANT:"
-echo " Rechargez votre shell: source ~/.bashrc"
-echo " Ou redémarrez votre terminal"
-echo ""
-echo "========================================================================"
\ No newline at end of file
+main "$@"
diff --git a/lib/core.sh b/lib/core.sh
new file mode 100644
index 0000000..938362f
--- /dev/null
+++ b/lib/core.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# lib/core.sh — Colors, palette, global state and shared constants for Ghostline.
+# Sourced by the entry point and by every module; never executed directly.
+
+if [[ -n "${GHOSTLINE_CORE_LOADED:-}" ]]; then
+ return 0
+fi
+GHOSTLINE_CORE_LOADED=1
+
+# ---------------------------------------------------------------------------
+# Color palette — TTY-aware. Pipes get plain text, terminals get colors.
+# ---------------------------------------------------------------------------
+if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 \
+ && [[ -n "${TERM:-}" ]] && [[ "${TERM}" != "dumb" ]]; then
+ RESET="$(tput sgr0)"
+ BOLD="$(tput bold)"
+ DIM="$(tput dim)"
+
+ RED="$(tput setaf 1)"
+ GREEN="$(tput setaf 2)"
+ YELLOW="$(tput setaf 3)"
+ BLUE="$(tput setaf 4)"
+ MAGENTA="$(tput setaf 5)"
+ CYAN="$(tput setaf 6)"
+
+ BRIGHT_RED="$(tput setaf 9)"
+ BRIGHT_GREEN="$(tput setaf 10)"
+ BRIGHT_MAGENTA="$(tput setaf 13)"
+else
+ RESET=""
+ BOLD=""
+ DIM=""
+ RED=""
+ GREEN=""
+ YELLOW=""
+ BLUE=""
+ MAGENTA=""
+ CYAN=""
+ BRIGHT_RED=""
+ BRIGHT_GREEN=""
+ BRIGHT_MAGENTA=""
+fi
+readonly RESET BOLD DIM RED GREEN YELLOW BLUE MAGENTA CYAN
+readonly BRIGHT_RED BRIGHT_GREEN BRIGHT_MAGENTA
+
+# ---------------------------------------------------------------------------
+# Global runtime state. Modules read and write these freely.
+# ---------------------------------------------------------------------------
+GHOSTLINE_TARGET=""
+GHOSTLINE_DOMAIN=""
+GHOSTLINE_USERNAME=""
+GHOSTLINE_PASSWORD=""
+GHOSTLINE_OUTPUT_DIR="ad_enum_$(date +%Y%m%d_%H%M%S)"
+
+# Where third-party tools may be cloned by install.sh.
+GHOSTLINE_TOOLS_DIR="${GHOSTLINE_TOOLS_DIR:-/opt}"
diff --git a/lib/installer.sh b/lib/installer.sh
new file mode 100644
index 0000000..87dc54c
--- /dev/null
+++ b/lib/installer.sh
@@ -0,0 +1,142 @@
+#!/usr/bin/env bash
+# lib/installer.sh — Logging, prompting and install primitives.
+# Sourced by both the runtime entry point and install.sh.
+
+if [[ -n "${GHOSTLINE_INSTALLER_LOADED:-}" ]]; then
+ return 0
+fi
+GHOSTLINE_INSTALLER_LOADED=1
+
+# ---------------------------------------------------------------------------
+# Logging — color-coded, with a consistent prefix.
+# ---------------------------------------------------------------------------
+log_step() { printf '%b[*]%b %s\n' "${BLUE}" "${RESET}" "$*"; }
+log_info() { printf '%b[i]%b %s\n' "${CYAN}" "${RESET}" "$*"; }
+log_warn() { printf '%b[!]%b %s\n' "${YELLOW}" "${RESET}" "$*" >&2; }
+log_error() { printf '%b[-]%b %s\n' "${RED}" "${RESET}" "$*" >&2; }
+log_success() { printf '%b[+]%b %s\n' "${GREEN}" "${RESET}" "$*"; }
+
+# ---------------------------------------------------------------------------
+# Prompting helpers — keep behavior consistent across modules.
+# ---------------------------------------------------------------------------
+prompt_value() {
+ local label="$1"
+ local default="${2:-}"
+ local response
+ if [[ -n "$default" ]]; then
+ read -rp "${label} [${default}]: " response
+ response="${response:-$default}"
+ else
+ read -rp "${label}: " response
+ fi
+ printf '%s' "$response"
+}
+
+prompt_password() {
+ local label="$1"
+ local response
+ read -rsp "${label}: " response
+ echo ""
+ printf '%s' "$response"
+}
+
+prompt_yesno() {
+ local label="$1"
+ local default="${2:-n}"
+ local hint
+ case "$default" in
+ y|Y) hint="Y/n" ;;
+ *) hint="y/N" ;;
+ esac
+ local response
+ read -rp "${label} [${hint}]: " response
+ response="${response:-$default}"
+ [[ "$response" =~ ^[Yy]([Ee][Ss])?$ ]]
+}
+
+press_enter_to_continue() {
+ read -rp "Press Enter to continue..." _
+}
+
+# ---------------------------------------------------------------------------
+# Runtime — verify a binary is on PATH or warn cleanly.
+# Returns 0 if the command exists, 1 otherwise.
+# ---------------------------------------------------------------------------
+ensure_command() {
+ local cmd="$1"
+ local hint="${2:-}"
+ if command -v "$cmd" >/dev/null 2>&1; then
+ return 0
+ fi
+ log_warn "Required command not found: ${cmd}"
+ if [[ -n "$hint" ]]; then
+ log_info "Hint: ${hint}"
+ else
+ log_info "Try running 'sudo ./install.sh' first."
+ fi
+ return 1
+}
+
+# Resolve the first available command from a list of alternatives.
+# Echoes the resolved path on stdout, or returns 1 if none are found.
+resolve_command() {
+ local candidate
+ for candidate in "$@"; do
+ if command -v "$candidate" >/dev/null 2>&1; then
+ command -v "$candidate"
+ return 0
+ fi
+ done
+ return 1
+}
+
+# ---------------------------------------------------------------------------
+# Install-time primitives — used by install.sh.
+# ---------------------------------------------------------------------------
+apt_install() {
+ apt install -y "$@"
+}
+
+pipx_install() {
+ local package="$1"
+ pipx install "$package" --force 2>&1 | grep -v "WARNING" || true
+}
+
+# Best-effort pip install with progressive fallbacks for modern Debian/Kali.
+pip_install() {
+ local package="$1"
+ python3 -m pip install --user --ignore-installed "$package" 2>/dev/null \
+ || python3 -m pip install --break-system-packages --ignore-installed "$package" 2>/dev/null \
+ || python3 -m pip install --user "$package" 2>/dev/null \
+ || python3 -m pip install --break-system-packages "$package" 2>/dev/null
+}
+
+install_pip_requirements() {
+ local req_dir="$1"
+ local req_file="${req_dir}/requirements.txt"
+ [[ -f "$req_file" ]] || return 0
+ local pkg
+ while IFS= read -r pkg; do
+ [[ -z "$pkg" || "$pkg" =~ ^# ]] && continue
+ pip_install "$pkg"
+ done < "$req_file"
+}
+
+# Clone a repo into or git pull --ff-only if it already exists.
+clone_or_pull() {
+ local url="$1"
+ local dest="$2"
+ if [[ -d "${dest}/.git" ]]; then
+ (cd "$dest" || return 1; git pull --ff-only) >/dev/null 2>&1
+ else
+ git clone "$url" "$dest" >/dev/null 2>&1
+ fi
+}
+
+# Source check used by install.sh.
+require_root() {
+ if [[ ${EUID} -ne 0 ]]; then
+ log_error "This script must be run as root (use sudo)."
+ exit 1
+ fi
+}
diff --git a/lib/modules/active.sh b/lib/modules/active.sh
new file mode 100644
index 0000000..e50328e
--- /dev/null
+++ b/lib/modules/active.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+# lib/modules/active.sh — Authenticated enumeration (credentials required).
+
+if [[ -n "${GHOSTLINE_MODULE_ACTIVE_LOADED:-}" ]]; then
+ return 0
+fi
+GHOSTLINE_MODULE_ACTIVE_LOADED=1
+
+active_run_bloodhound() {
+ printf '\n%bBloodHound Collection%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ require_domain
+ require_credentials
+ ensure_command "bloodhound-python" "pipx install bloodhound" || return 0
+ ensure_output_dir
+ log_step "Collecting BloodHound data..."
+ bloodhound-python \
+ -u "${GHOSTLINE_USERNAME}" -p "${GHOSTLINE_PASSWORD}" \
+ -d "${GHOSTLINE_DOMAIN}" -ns "${GHOSTLINE_TARGET}" \
+ -c all --zip -o "${GHOSTLINE_OUTPUT_DIR}"
+ log_success "JSON files generated"
+ press_enter_to_continue
+}
+
+active_run_cme() {
+ printf '\n%bCrackMapExec%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ require_credentials
+ local cme
+ if ! cme=$(resolve_command "crackmapexec" "cme" "nxc"); then
+ log_warn "Neither crackmapexec, cme nor nxc is installed."
+ log_info "Hint: pipx install crackmapexec (or pipx install netexec)"
+ return 0
+ fi
+ ensure_output_dir
+ log_step "Running ${cme##*/} enumeration..."
+ "$cme" smb "${GHOSTLINE_TARGET}" \
+ -u "${GHOSTLINE_USERNAME}" -p "${GHOSTLINE_PASSWORD}" --shares \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/cme_shares.txt"
+ "$cme" smb "${GHOSTLINE_TARGET}" \
+ -u "${GHOSTLINE_USERNAME}" -p "${GHOSTLINE_PASSWORD}" --users \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/cme_users.txt"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+
+active_run_adidns() {
+ printf '\n%bAD DNS Dump%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ require_domain
+ require_credentials
+ ensure_command "adidnsdump" "pip install adidnsdump" || return 0
+ ensure_output_dir
+ log_step "Dumping DNS records..."
+ adidnsdump \
+ -u "${GHOSTLINE_DOMAIN}\\${GHOSTLINE_USERNAME}" \
+ -p "${GHOSTLINE_PASSWORD}" \
+ "${GHOSTLINE_TARGET}" -r \
+ --output "${GHOSTLINE_OUTPUT_DIR}/dns.csv"
+ log_success "DNS records saved"
+ press_enter_to_continue
+}
+
+active_run_getnpusers() {
+ printf '\n%bGetNPUsers (ASREPRoast)%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ require_domain
+ local getnp
+ if ! getnp=$(resolve_command "GetNPUsers.py" "impacket-GetNPUsers"); then
+ log_warn "GetNPUsers.py / impacket-GetNPUsers is not installed."
+ log_info "Hint: pipx install impacket"
+ return 0
+ fi
+ ensure_output_dir
+ log_step "Searching for AS-REP roastable accounts..."
+ "$getnp" "${GHOSTLINE_DOMAIN}/" -dc-ip "${GHOSTLINE_TARGET}" -no-pass \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/asreproast.txt"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+
+active_run_ridenum() {
+ printf '\n%bRID Enumeration%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ ensure_command "ridenum" "sudo ./install.sh" || return 0
+ ensure_output_dir
+ log_step "Enumerating RIDs (500..10000)..."
+ ridenum "${GHOSTLINE_TARGET}" 500 10000 \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/ridenum.txt"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+
+handle_active_menu() {
+ local choice
+ while true; do
+ clear
+ display_banner_with_menu "active"
+ prompt_menu_choice "Active Enum"
+ read -r choice
+
+ case "$choice" in
+ 1) active_run_bloodhound ;;
+ 2) active_run_cme ;;
+ 3) active_run_adidns ;;
+ 4) active_run_getnpusers ;;
+ 5) active_run_ridenum ;;
+ 0) return ;;
+ *)
+ printf '\n%bInvalid choice!%b\n' "${BRIGHT_RED}" "${RESET}"
+ sleep 1
+ ;;
+ esac
+ done
+}
diff --git a/lib/modules/config.sh b/lib/modules/config.sh
new file mode 100644
index 0000000..af38528
--- /dev/null
+++ b/lib/modules/config.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+# lib/modules/config.sh — Target, domain, credentials and output configuration.
+
+if [[ -n "${GHOSTLINE_MODULE_CONFIG_LOADED:-}" ]]; then
+ return 0
+fi
+GHOSTLINE_MODULE_CONFIG_LOADED=1
+
+# ---------------------------------------------------------------------------
+# Quick checks used by enumeration modules. Each prompts the user if the
+# corresponding global is empty.
+# ---------------------------------------------------------------------------
+require_target() {
+ if [[ -z "${GHOSTLINE_TARGET}" ]]; then
+ log_warn "No target set."
+ GHOSTLINE_TARGET=$(prompt_value "Enter target IP/hostname")
+ fi
+}
+
+require_domain() {
+ if [[ -z "${GHOSTLINE_DOMAIN}" ]]; then
+ log_info "No domain set."
+ GHOSTLINE_DOMAIN=$(prompt_value "Enter domain (e.g. domain.local)")
+ fi
+}
+
+require_credentials() {
+ if [[ -z "${GHOSTLINE_USERNAME}" || -z "${GHOSTLINE_PASSWORD}" ]]; then
+ log_warn "Credentials not set."
+ GHOSTLINE_USERNAME=$(prompt_value "Username")
+ GHOSTLINE_PASSWORD=$(prompt_password "Password")
+ fi
+}
+
+ensure_output_dir() {
+ mkdir -p "${GHOSTLINE_OUTPUT_DIR}"
+}
+
+# ---------------------------------------------------------------------------
+# Configuration menu actions.
+# ---------------------------------------------------------------------------
+config_set_target() {
+ printf '\n%bSetting Target%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ GHOSTLINE_TARGET=$(prompt_value "Target IP/hostname")
+ log_success "Target set: ${GHOSTLINE_TARGET}"
+ press_enter_to_continue
+}
+
+config_set_domain() {
+ printf '\n%bSetting Domain%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ GHOSTLINE_DOMAIN=$(prompt_value "Domain name")
+ log_success "Domain set: ${GHOSTLINE_DOMAIN}"
+ press_enter_to_continue
+}
+
+config_set_credentials() {
+ printf '\n%bSetting Credentials%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ GHOSTLINE_USERNAME=$(prompt_value "Username")
+ GHOSTLINE_PASSWORD=$(prompt_password "Password")
+ log_success "Credentials configured"
+ press_enter_to_continue
+}
+
+config_set_output_dir() {
+ printf '\n%bSetting Output Directory%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ local custom_dir
+ custom_dir=$(prompt_value "Directory name" "${GHOSTLINE_OUTPUT_DIR}")
+ GHOSTLINE_OUTPUT_DIR="${custom_dir}"
+ mkdir -p "${GHOSTLINE_OUTPUT_DIR}"
+ log_success "Output: ${GHOSTLINE_OUTPUT_DIR}"
+ press_enter_to_continue
+}
+
+handle_config_menu() {
+ local choice
+ while true; do
+ clear
+ display_banner_with_menu "config"
+ prompt_menu_choice "Configure"
+ read -r choice
+
+ case "$choice" in
+ 1) config_set_target ;;
+ 2) config_set_domain ;;
+ 3) config_set_credentials ;;
+ 4) config_set_output_dir ;;
+ 0) return ;;
+ *)
+ printf '\n%bInvalid choice!%b\n' "${BRIGHT_RED}" "${RESET}"
+ sleep 1
+ ;;
+ esac
+ done
+}
diff --git a/lib/modules/passive.sh b/lib/modules/passive.sh
new file mode 100644
index 0000000..ab40d85
--- /dev/null
+++ b/lib/modules/passive.sh
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+# lib/modules/passive.sh — Passive reconnaissance (no credentials required).
+
+if [[ -n "${GHOSTLINE_MODULE_PASSIVE_LOADED:-}" ]]; then
+ return 0
+fi
+GHOSTLINE_MODULE_PASSIVE_LOADED=1
+
+# Convert a dotted domain (corp.local) into an LDAP base DN (dc=corp,dc=local).
+_domain_to_basedn() {
+ local domain="$1"
+ printf 'dc=%s' "${domain//./,dc=}"
+}
+
+passive_run_nmap() {
+ printf '\n%bNmap Scan%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ ensure_command "nmap" "sudo apt install nmap" || return 0
+ ensure_output_dir
+ log_step "Scanning AD ports on ${GHOSTLINE_TARGET}..."
+ nmap -p 88,135,139,389,445,464,636,3268,3269,5985 -sV -sC \
+ -oA "${GHOSTLINE_OUTPUT_DIR}/nmap_ad" "${GHOSTLINE_TARGET}"
+ log_success "Results saved to ${GHOSTLINE_OUTPUT_DIR}/nmap_ad.*"
+ press_enter_to_continue
+}
+
+passive_run_enum4linux() {
+ printf '\n%bEnum4linux-ng%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ ensure_command "enum4linux-ng" "sudo ./install.sh" || return 0
+ ensure_output_dir
+ log_step "Running enum4linux-ng..."
+ enum4linux-ng -A "${GHOSTLINE_TARGET}" \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/enum4linux-ng.txt"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+
+passive_run_rpc() {
+ printf '\n%bRPC Client%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ ensure_command "rpcclient" "sudo apt install samba-common-bin" || return 0
+ ensure_output_dir
+ log_step "Attempting RPC null session..."
+ {
+ echo "srvinfo"
+ echo "enumdomusers"
+ echo "enumdomgroups"
+ echo "exit"
+ } | rpcclient -U "" "${GHOSTLINE_TARGET}" -N \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/rpcclient.txt"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+
+passive_run_ldap() {
+ printf '\n%bLDAP Search%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ require_domain
+ ensure_command "ldapsearch" "sudo apt install ldap-utils" || return 0
+ ensure_output_dir
+ local base_dn
+ base_dn=$(_domain_to_basedn "${GHOSTLINE_DOMAIN}")
+ log_step "Querying LDAP (base ${base_dn})..."
+ ldapsearch -x -H "ldap://${GHOSTLINE_TARGET}" -b "${base_dn}" "(objectclass=*)" \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/ldap.txt"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+
+passive_run_dns() {
+ printf '\n%bDNS Enumeration%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ require_domain
+ ensure_command "dnsrecon" "sudo apt install dnsrecon" || return 0
+ ensure_output_dir
+ log_step "Running dnsrecon..."
+ dnsrecon -d "${GHOSTLINE_DOMAIN}" -n "${GHOSTLINE_TARGET}" \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/dnsrecon.txt"
+ log_success "Results saved"
+ press_enter_to_continue
+}
+
+handle_passive_menu() {
+ local choice
+ while true; do
+ clear
+ display_banner_with_menu "passive"
+ prompt_menu_choice "Passive Recon"
+ read -r choice
+
+ case "$choice" in
+ 1) passive_run_nmap ;;
+ 2) passive_run_enum4linux ;;
+ 3) passive_run_rpc ;;
+ 4) passive_run_ldap ;;
+ 5) passive_run_dns ;;
+ 0) return ;;
+ *)
+ printf '\n%bInvalid choice!%b\n' "${BRIGHT_RED}" "${RESET}"
+ sleep 1
+ ;;
+ esac
+ done
+}
diff --git a/lib/modules/special.sh b/lib/modules/special.sh
new file mode 100644
index 0000000..14d18c8
--- /dev/null
+++ b/lib/modules/special.sh
@@ -0,0 +1,112 @@
+#!/usr/bin/env bash
+# lib/modules/special.sh — Automated workflow, SMB vulns, secrets dump, results.
+
+if [[ -n "${GHOSTLINE_MODULE_SPECIAL_LOADED:-}" ]]; then
+ return 0
+fi
+GHOSTLINE_MODULE_SPECIAL_LOADED=1
+
+special_run_workflow() {
+ printf '\n%b%bAUTO WORKFLOW%b\n' "${BRIGHT_MAGENTA}" "${BOLD}" "${RESET}"
+ require_target
+ ensure_output_dir
+ log_step "Running automated enumeration workflow..."
+ echo ""
+
+ if ensure_command "nmap" "sudo apt install nmap"; then
+ log_step "Phase 1: Network Scan"
+ nmap -p 88,135,139,389,445 -sV "${GHOSTLINE_TARGET}" \
+ -oA "${GHOSTLINE_OUTPUT_DIR}/nmap" 2>/dev/null
+ fi
+
+ if ensure_command "enum4linux-ng" "sudo ./install.sh"; then
+ log_step "Phase 2: SMB Enumeration"
+ enum4linux-ng -A "${GHOSTLINE_TARGET}" \
+ > "${GHOSTLINE_OUTPUT_DIR}/enum4linux.txt" 2>/dev/null
+ fi
+
+ if ensure_command "rpcclient" "sudo apt install samba-common-bin"; then
+ log_step "Phase 3: RPC Enumeration"
+ { echo "enumdomusers"; echo "exit"; } \
+ | rpcclient -U "" "${GHOSTLINE_TARGET}" -N \
+ > "${GHOSTLINE_OUTPUT_DIR}/rpc.txt" 2>/dev/null
+ fi
+
+ if [[ -n "${GHOSTLINE_DOMAIN}" ]] && ensure_command "ldapsearch" "sudo apt install ldap-utils"; then
+ log_step "Phase 4: LDAP Query"
+ local base_dn="dc=${GHOSTLINE_DOMAIN//./,dc=}"
+ ldapsearch -x -H "ldap://${GHOSTLINE_TARGET}" -b "${base_dn}" "(objectclass=user)" \
+ > "${GHOSTLINE_OUTPUT_DIR}/ldap.txt" 2>/dev/null
+ fi
+
+ echo ""
+ log_success "Workflow complete. Results in ${GHOSTLINE_OUTPUT_DIR}/"
+ press_enter_to_continue
+}
+
+special_run_smb_vulns() {
+ printf '\n%bSMB Vulnerabilities%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ ensure_command "nmap" "sudo apt install nmap" || return 0
+ ensure_output_dir
+ log_step "Scanning for SMB vulnerabilities..."
+ nmap -p 445 --script 'smb-vuln*' "${GHOSTLINE_TARGET}" \
+ -oA "${GHOSTLINE_OUTPUT_DIR}/smb_vulns"
+ log_success "Vulnerability scan complete"
+ press_enter_to_continue
+}
+
+special_run_secretsdump() {
+ printf '\n%bSecrets Dump%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ require_target
+ require_domain
+ require_credentials
+ local secretsdump
+ if ! secretsdump=$(resolve_command "secretsdump.py" "impacket-secretsdump"); then
+ log_warn "secretsdump.py / impacket-secretsdump is not installed."
+ log_info "Hint: pipx install impacket"
+ return 0
+ fi
+ ensure_output_dir
+ printf '%b%b! This requires elevated privileges!%b\n' \
+ "${BRIGHT_RED}" "${BOLD}" "${RESET}"
+ log_step "Dumping secrets..."
+ "$secretsdump" \
+ "${GHOSTLINE_DOMAIN}/${GHOSTLINE_USERNAME}:${GHOSTLINE_PASSWORD}@${GHOSTLINE_TARGET}" \
+ | tee "${GHOSTLINE_OUTPUT_DIR}/secrets.txt"
+ log_success "Secrets saved"
+ press_enter_to_continue
+}
+
+special_view_results() {
+ printf '\n%bResults Viewer%b\n' "${BRIGHT_MAGENTA}" "${RESET}"
+ if [[ ! -d "${GHOSTLINE_OUTPUT_DIR}" ]]; then
+ log_warn "No results directory found at ${GHOSTLINE_OUTPUT_DIR}"
+ else
+ log_info "Files in ${GHOSTLINE_OUTPUT_DIR}:"
+ ls -lh "${GHOSTLINE_OUTPUT_DIR}"
+ fi
+ press_enter_to_continue
+}
+
+handle_special_menu() {
+ local choice
+ while true; do
+ clear
+ display_banner_with_menu "special"
+ prompt_menu_choice "Special Ops"
+ read -r choice
+
+ case "$choice" in
+ 1) special_run_workflow ;;
+ 2) special_run_smb_vulns ;;
+ 3) special_run_secretsdump ;;
+ 4) special_view_results ;;
+ 0) return ;;
+ *)
+ printf '\n%bInvalid choice!%b\n' "${BRIGHT_RED}" "${RESET}"
+ sleep 1
+ ;;
+ esac
+ done
+}
diff --git a/lib/ui.sh b/lib/ui.sh
new file mode 100644
index 0000000..924227e
--- /dev/null
+++ b/lib/ui.sh
@@ -0,0 +1,308 @@
+#!/usr/bin/env bash
+# lib/ui.sh — ASCII art, menus and banner rendering.
+
+if [[ -n "${GHOSTLINE_UI_LOADED:-}" ]]; then
+ return 0
+fi
+GHOSTLINE_UI_LOADED=1
+
+# ---------------------------------------------------------------------------
+# Decorative ASCII raven displayed alongside each menu.
+# ---------------------------------------------------------------------------
+GHOSTLINE_ASCII_ART=$(cat <<'ASCII'
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⢖⣠⣄⡀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣾⣿⣿⠎⠀⠀⠹⡀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⣿⣿⣿⣿⢿⣤⠴⠒⢦⡇⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⠿⠛⠁⠸⡇⠀⠀⠀⡇⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣿⣿⣿⠿⠋⠁⠀⠀⠀⠀⣧⠀⠀⠀⡇⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⠂⠾⠿⣿⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⢠⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣷⣤⡀⠈⠉⠑⠒⠤⢀⡀⠀⠀⠀⠀⣿⠀⠀⠀⣼⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠟⠿⠿⢿⣿⣿⣆⠀⠀⠀⠀⠀⠈⠑⢄⠀⠀⣿⠀⠀⠀⡏⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⡏⡄⠀⠀⠀⠈⠻⣿⣧⠀⠀⠀⠀⠀⠀⠀⢳⠀⣿⠀⠀⠀⡇⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⡀⣷⠄⠀⠀⠀⠀⠙⢿⣇⡀⠀⠀⠀⠀⠀⠀⣷⡇⠀⠀⢸⡇⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⡿⠐⣁⣀⣀⢀⣾⣤⢤⣶⣿⣿⣦⡀⠀⠀⠀⠀⢸⠇⠀⠀⡾⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣇⣾⣿⣿⣿⡇⠀⠻⣽⣿⣿⣿⡿⠿⣦⠀⠀⠀⡟⠀⠀⢰⠇⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⡟⣌⡻⠛⠁⢰⠸⡔⣤⣉⡩⠐⠁⠀⢸⡇⠀⢰⠃⠀⠀⡎⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣇⠀⠉⠀⠀⠀⠀⠀⠈⠙⠻⣶⢶⣶⣾⠇⢀⡏⠀⠀⠰⢣⠀⠀⠀
+⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⢋⣿⣿⣿⣿⣿⣿⣿⣷⠀⠤⠒⠒⠓⠘⠀⢴⢏⡟⠈⣿⠀⡞⠀⠀⢀⡇⠀⠣⡀⠀
+⠀⠀⠀⠀⠀⠀⣼⣿⡿⠁⣸⣿⣿⣿⣿⣿⣿⣿⣿⠀⡰⠞⠛⠛⠛⠳⠶⠿⠁⣰⣿⣼⠃⠀⠀⡼⢿⣶⡀⡇⠀
+⠀⠀⠀⠀⠀⣼⣿⠟⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣿⠟⠋⠉⠉⠁⠀⠀⢠⣿⣿⠃⠀⠀⠰⠁⠘⢿⠔⢀⣠
+⠀⠀⠀⠀⣰⣿⡟⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣏⠻⠖⠂⠈⠒⠂⢀⣠⣿⣿⠏⠀⠀⢀⣧⣶⡶⢖⣿⠇⡿
+⠀⠀⠀⢠⣿⡟⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣿⣏⠩⠭⣼⣿⣿⠏⠀⠀⠀⡼⠛⢉⣴⣿⠏⣸⡇
+⠀⠀⢀⣾⡟⠀⠀⠀⠀⠀⠀⠀⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⠏⠀⠀⠀⡼⢁⣴⣿⡿⠁⣰⣿⠁
+⠀⠀⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠿⠿⠿⠛⢛⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⣰⣿⣿⡿⠋⠀⣰⣿⡟⢠
+⠀⠀⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⢠⣿⡿⠛⠁⠀⣼⣿⣿⣧⣿
+⠀⢸⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⡿⠟⢻⠐⠉⠑⠤⣀⢠⣿⣿⠀⠀⢀⣾⣿⣿⣿⣿⣿
+⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⢱⠹⠉⢉⠉⠓⠶⠤⠍⠛⢻⡄⣠⣿⣿⣿⣿⣿⣿⣿
+⢸⠀⠀⠀⠀⠀⠀⢀⣤⣴⡖⠤⣀⠀⠀⠀⣼⣿⣿⣿⠉⠉⠑⠛⠛⠛⠳⢄⣀⠈⢹⠤⣿⣿⣿⣿⣿⣿⣿⣿⣿
+ASCII
+)
+
+# ---------------------------------------------------------------------------
+# Title screens for each menu.
+# ---------------------------------------------------------------------------
+generate_main_menu() {
+ local -a menu_lines=(
+ "${BRIGHT_RED} ▄██████▄ ▄█ █▄ ▄██████▄ ▄████████ ███ ▄█ ▄█ ███▄▄▄▄ ▄████████${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ▀█████████▄ ███ ███ ███▀▀▀██▄ ███ ███${RESET}"
+ "${BRIGHT_RED} ███ █▀ ███ ███ ███ ███ ███ █▀ ▀███▀▀██ ███ ███▌ ███ ███ ███ █▀${RESET}"
+ "${BRIGHT_RED} ▄███ ▄███▄▄▄▄███▄▄ ███ ███ ███ ███ ▀ ███ ███▌ ███ ███ ▄███▄▄▄${RESET}"
+ "${BRIGHT_RED}▀▀███ ████▄ ▀▀███▀▀▀▀███▀ ███ ███ ▀███████████ ███ ███ ███▌ ███ ███ ▀▀███▀▀▀${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ▄█ ███ ███ ███▌ ▄ ███ ███ ███ ███ ███${RESET}"
+ "${BRIGHT_RED} ████████▀ ███ █▀ ▀██████▀ ▄████████▀ ▄████▀ █████▄▄██ █▀ ▀█ █▀ ██████████${RESET}"
+ " "
+ "${BRIGHT_RED}█"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_MAGENTA}Configuration:${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} Target: ${BRIGHT_MAGENTA}${GHOSTLINE_TARGET:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} Domain: ${BRIGHT_MAGENTA}${GHOSTLINE_DOMAIN:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} User: ${BRIGHT_MAGENTA}${GHOSTLINE_USERNAME:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_MAGENTA}Hunt for Active Directory intelligence:${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Configuration Menu"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} Passive Enumeration"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} Active Enumeration"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} Special Actions"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Exit"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ )
+ printf '%s\n' "${menu_lines[@]}"
+}
+
+generate_config_menu() {
+ local -a menu_lines=(
+ "${BRIGHT_RED} ▄████████ ▄██████▄ ███▄▄▄▄ ▄████████ ▄█ ▄██████▄ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ███ ███ ███ ${RESET}"
+ "${BRIGHT_RED} ███ █▀ ███ ███ ███ ███ ███ █▀ ███▌ ███ █▀ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ▄███▄▄▄ ███▌ ▄███ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ▀▀███▀▀▀ ███▌ ▀▀███ ████▄ ${RESET}"
+ "${BRIGHT_RED} ███ █▄ ███ ███ ███ ███ ███ ███ ███ ███ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ${RESET}"
+ "${BRIGHT_RED} ████████▀ ▀██████▀ ▀█ █▀ ███ █▀ ████████▀ ${RESET}"
+ " "
+ "${BRIGHT_MAGENTA}Configure your hunting parameters${RESET}"
+ " "
+ "${BOLD}${BRIGHT_RED}█${RESET} Current Configuration:"
+ "${BOLD}${BRIGHT_RED}█${RESET} Target: ${BRIGHT_MAGENTA}${GHOSTLINE_TARGET:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} Domain: ${BRIGHT_MAGENTA}${GHOSTLINE_DOMAIN:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} User: ${BRIGHT_MAGENTA}${GHOSTLINE_USERNAME:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} Output: ${BRIGHT_MAGENTA}${GHOSTLINE_OUTPUT_DIR}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Set Target (IP/Hostname)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} Set Domain"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} Set Credentials"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} Set Output Directory"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ )
+ printf '%s\n' "${menu_lines[@]}"
+}
+
+generate_passive_menu() {
+ local -a menu_lines=(
+ "${BRIGHT_RED} ▄███████▄ ▄████████ ▄████████ ▄████████ ▄█ ███ ▄█ █▄ ▄████████${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ▀█████████▄ ███ ███ ███ ███${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ █▀ ███ █▀ ███▌ ▀███▀▀██ ███ ███ ███ █▀ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███▌ ███ ▀ ███ ███ ▄███▄▄▄ ${RESET}"
+ "${BRIGHT_RED}▀█████████▀ ▀███████████ ▀███████████ ▀███████████ ███▌ ███ ███ ███ ▀▀███▀▀▀ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ▄█ ███ ▄█ ███ ███ ███ ███ ███ ███ ███${RESET}"
+ "${BRIGHT_RED} ▄████▀ ███ █▀ ▄████████▀ ▄████████▀ █▀ ▄████▀ ▀██████▀ ██████████${RESET}"
+ " "
+ "${BRIGHT_MAGENTA}Silent reconnaissance without credentials${RESET}"
+ " "
+ "${BOLD}${BRIGHT_RED}█${RESET} Target: ${BRIGHT_MAGENTA}${GHOSTLINE_TARGET:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Nmap Scan (Port Discovery)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} Enum4linux-ng (SMB Enumeration)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} RPC Client (Null Session)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} LDAP Search (Anonymous)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[5]${RESET} DNS Enumeration"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ )
+ printf '%s\n' "${menu_lines[@]}"
+}
+
+generate_active_menu() {
+ local -a menu_lines=(
+ "${BRIGHT_RED} ▄████████ ▄████████ ███ ▄█ ███ ▄█ █▄ ▄████████${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ▀█████████▄ ███ ▀█████████▄ ███ ███ ███ ███${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ █▀ ▀███▀▀██ ███▌ ▀███▀▀██ ███ ███ ███ █▀ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ▀ ███▌ ███ ▀ ███ ███ ▄███▄▄▄ ${RESET}"
+ "${BRIGHT_RED} ▀███████████ ███ ███ ███▌ ███ ███ ███ ▀▀███▀▀▀ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ █▄ ███ ███ ███ ███ ███ ███ █▄ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███${RESET}"
+ "${BRIGHT_RED} ███ █▀ ████████▀ ▄████▀ █▀ ▄████▀ ▀██████▀ ██████████${RESET}"
+ " "
+ "${BRIGHT_MAGENTA}Authenticated enumeration with credentials${RESET}"
+ " "
+ "${BOLD}${BRIGHT_RED}█${RESET} Credentials: ${BRIGHT_MAGENTA}${GHOSTLINE_USERNAME:-Not set}${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} BloodHound Collection"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} CrackMapExec (Full Enum)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} AD DNS Dump"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} GetNPUsers (ASREPRoast)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[5]${RESET} RID Enumeration"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ )
+ printf '%s\n' "${menu_lines[@]}"
+}
+
+generate_special_menu() {
+ local -a menu_lines=(
+ "${BRIGHT_RED} ▄████████ ▄███████▄ ▄████████ ▄████████ ▄█ ▄████████ ▄█ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ${RESET}"
+ "${BRIGHT_RED} ███ █▀ ███ ███ ███ █▀ ███ █▀ ███▌ ███ ███ ███ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ▄███▄▄▄ ███ ███▌ ███ ███ ███ ${RESET}"
+ "${BRIGHT_RED}▀███████████ ▀█████████▀ ▀▀███▀▀▀ ███ ███▌ ▀███████████ ███ ${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ █▄ ███ █▄ ███ ███ ███ ███ ${RESET}"
+ "${BRIGHT_RED} ▄█ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▌ ▄ ${RESET}"
+ "${BRIGHT_RED} ▄████████▀ ▄████▀ ██████████ ████████▀ █▀ ███ █▀ █████▄▄██ ${RESET}"
+ " "
+ "${BRIGHT_MAGENTA}Advanced operations and automated workflows${RESET}"
+ " "
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[1]${RESET} Auto Workflow (Full Scan)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[2]${RESET} SMB Vulnerabilities Scan"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[3]${RESET} Secrets Dump (Requires Admin)"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[4]${RESET} View Results"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET} ${BRIGHT_RED}[0]${RESET} Back to Main Menu"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ "${BOLD}${BRIGHT_RED}█${RESET}"
+ )
+ printf '%s\n' "${menu_lines[@]}"
+}
+
+# ---------------------------------------------------------------------------
+# Splash screen displayed when the toolkit boots.
+# ---------------------------------------------------------------------------
+GHOSTLINE_INFO_PANEL=(
+ "${BRIGHT_RED} ▄██████▄ ▄█ █▄ ▄██████▄ ▄████████ ███ ▄█ ▄█ ███▄▄▄▄ ▄████████${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ▀█████████▄ ███ ███ ███▀▀▀██▄ ███ ███${RESET}"
+ "${BRIGHT_RED} ███ █▀ ███ ███ ███ ███ ███ █▀ ▀███▀▀██ ███ ███▌ ███ ███ ███ █▀${RESET}"
+ "${BRIGHT_RED} ▄███ ▄███▄▄▄▄███▄▄ ███ ███ ███ ███ ▀ ███ ███▌ ███ ███ ▄███▄▄▄${RESET}"
+ "${BRIGHT_RED}▀▀███ ████▄ ▀▀███▀▀▀▀███▀ ███ ███ ▀███████████ ███ ███ ███▌ ███ ███ ▀▀███▀▀▀${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▄${RESET}"
+ "${BRIGHT_RED} ███ ███ ███ ███ ███ ███ ▄█ ███ ███ ███▌ ▄ ███ ███ ███ ███ ███${RESET}"
+ "${BRIGHT_RED} ████████▀ ███ █▀ ▀██████▀ ▄████████▀ ▄████▀ █████▄▄██ █▀ ▀█ █▀ ██████████${RESET}"
+ ""
+ "${BRIGHT_MAGENTA}${BOLD}Active Directory OSINT Toolkit${RESET}"
+ "${DIM}by Melvin PETIT${RESET}"
+)
+
+_strip_ansi() {
+ sed -E 's/\x1B\[[0-9;?]*[ -/]*[@-~]//g; s/\x1B\][^\a]*\a//g'
+}
+
+display_title_middle_screen() {
+ local cols rows
+ cols=$(tput cols 2>/dev/null || echo 80)
+ rows=$(tput lines 2>/dev/null || echo 24)
+
+ local -a lines=( "${GHOSTLINE_INFO_PANEL[@]}" )
+ local h=${#lines[@]}
+
+ local max_w=0 raw visible_len line
+ for line in "${lines[@]}"; do
+ raw=$(printf "%s" "$line" | _strip_ansi)
+ visible_len=${#raw}
+ (( visible_len > max_w )) && max_w=$visible_len
+ done
+
+ local top=$(( (rows - h) / 2 ))
+ (( top < 0 )) && top=0
+ local left=$(( (cols - max_w) / 2 ))
+ (( left < 0 )) && left=0
+
+ printf "\033c"
+ local i
+ for ((i=0; i menu_count ? ascii_count : menu_count ))
+
+ local max_ascii_width=0 line
+ for line in "${ascii_lines[@]}"; do
+ (( ${#line} > max_ascii_width )) && max_ascii_width=${#line}
+ done
+
+ local spacing=" "
+ local i
+ for ((i=0; i