From 6517048b020f4afa967e0c45f7ecacc227ca6be4 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 09:31:30 -0400
Subject: [PATCH 01/47] Add new ui and new installation
---
ghost/core/console.py | 131 +++++++++++++++++++++++++++++++++++++++
install.py | 140 ++++++++++++++++++++++++++++++++++++++++++
requirements.txt | 5 ++
setup.py | 89 ++++++++++++---------------
uninstall.py | 86 ++++++++++++++++++++++++++
5 files changed, 402 insertions(+), 49 deletions(-)
create mode 100755 install.py
create mode 100644 requirements.txt
create mode 100644 uninstall.py
diff --git a/ghost/core/console.py b/ghost/core/console.py
index 1abded23..3e62ece1 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -26,6 +26,27 @@
from ghost.core.device import Device
+# --- Rich UI imports for styling (visual-only; logic untouched) ---
+from rich.console import Console as RichConsole
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from rich.align import Align
+from rich import box
+from rich.style import Style
+from rich.rule import Rule
+from rich.padding import Padding
+from rich.columns import Columns
+from rich.markdown import Markdown
+
+# Theme colors
+PURPLE = "#7B61FF"
+WHITE_ON_PURPLE = Style(color="white", bgcolor=PURPLE, bold=True)
+INFO_STYLE = Style(color=PURPLE, bold=True)
+WARN_STYLE = Style(color="yellow", bold=True)
+ERR_STYLE = Style(color="red", bold=True)
+SUCCESS_STYLE = Style(color="green", bold=True)
+
class Console(Cmd):
""" Subclass of ghost.core module.
@@ -35,6 +56,7 @@ class Console(Cmd):
"""
def __init__(self) -> None:
+ # keep original prompt/intro tokens so framework behavior is unchanged
super().__init__(
prompt='(%lineghost%end)> ',
intro="""%clear%end
@@ -49,8 +71,117 @@ def __init__(self) -> None:
"""
)
+ # preserve devices dict & logic
self.devices = {}
+ # create rich console for all visual outputs
+ self.rich = RichConsole()
+ self._render_header()
+
+ # -------------------------
+ # Visual helpers (only)
+ # -------------------------
+ def _render_header(self) -> None:
+ """Render a fancy hacker-style header with tools table."""
+ title = Text("Ghost Framework 8.0.0", style="bold white")
+ subtitle = Text("Developed by EntySec — https://entysec.com/", style="dim")
+
+ # ASCII art panel (kept similar to original)
+ ascii_art = Text(
+ " .--. .-. .-.\n"
+ " : .--': : .' `.\n"
+ " : : _ : `-. .--. .--.`. .'\n"
+ " : :; :: .. :' .; :`._-.': :\n"
+ " `.__.':_;:_;`.__.'`.__.':_;",
+ justify="center",
+ )
+
+ left = Panel(
+ Align.center(ascii_art),
+ border_style=PURPLE,
+ box=box.HEAVY,
+ padding=(0, 2),
+ title="[bold]" + "GHOST",
+ subtitle=title,
+ )
+
+ # Tools / Commands table (visual only)
+ tools_table = Table.grid(expand=True)
+ tools_table.add_column(ratio=1)
+ tools_table.add_column(ratio=2)
+
+ tools = Table(box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
+ tools.add_column("Command", style="bold white", no_wrap=True)
+ tools.add_column("Description", style="dim")
+
+ tools.add_row("connect :[port]", "Connect to device via ADB (default port 5555)")
+ tools.add_row("devices", "List connected devices")
+ tools.add_row("disconnect ", "Disconnect device by ID")
+ tools.add_row("interact ", "Interact with a connected device")
+ tools.add_row("exit", "Quit Ghost Framework")
+ tools.add_row("Index 99", "Return to Menu / Exit (UI helper)")
+
+ right_panel = Panel(
+ Align.left(
+ Text.assemble(subtitle, "\n\n", "Theme: ", (PURPLE, "Hacker • Purple"))
+ ),
+ border_style=PURPLE,
+ box=box.ROUNDED,
+ padding=(0, 1),
+ title="[bold]Info",
+ )
+
+ # combine header columns
+ header_columns = Columns([left, Panel(Padding(tools, (1, 2)), border_style=PURPLE), right_panel])
+ self.rich.print(header_columns)
+ self.rich.print(Rule(style=PURPLE))
+ self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
+ self.rich.print()
+
+ def print_empty(self, message: str = "") -> None:
+ # preserve signature and behavior, but print a small spacer
+ self.rich.print("") # purely visual
+
+ def print_information(self, message: str) -> None:
+ # visually enhanced info box (preserves semantics)
+ self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
+
+ def print_warning(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="yellow", title="[bold white]WARNING", box=box.MINIMAL))
+
+ def print_error(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="red", title="[bold white]ERROR", box=box.MINIMAL))
+
+ def print_success(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="green", title="[bold white]SUCCESS", box=box.MINIMAL))
+
+ def print_usage(self, usage: str) -> None:
+ # show usage in an emphasized box
+ usage_text = Text.assemble(("Usage: ", "bold"), (usage, ""))
+ footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
+ self.rich.print(Panel(usage_text, border_style=PURPLE, title="[bold]USAGE", subtitle=footer))
+
+ def print_process(self, message: str) -> None:
+ # lightweight status indicator (visual only)
+ with self.rich.status(Text(message, style=INFO_STYLE), spinner="bouncingBall", spinner_style=PURPLE):
+ # no blocking logic here — just visual affordance
+ pass
+
+ def print_table(self, title: str, columns: tuple, *rows) -> None:
+ """Render a stylized table for lists like connected devices."""
+ table = Table(title=title, box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
+ for col in columns:
+ table.add_column(str(col), header_style="bold white")
+ for row in rows:
+ # ensure each item is string (matching original behavior)
+ table.add_row(*[str(x) for x in row])
+ footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
+ wrapper = Panel(Padding(table, (0, 1)), subtitle=footer, border_style=PURPLE)
+ self.rich.print(wrapper)
+
+ # -------------------------
+ # Original command logic (UNCHANGED)
+ # -------------------------
def do_exit(self, _) -> None:
""" Exit Ghost Framework.
diff --git a/install.py b/install.py
new file mode 100755
index 00000000..2ffb708b
--- /dev/null
+++ b/install.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+"""
+install.py — Ghost installer (full)
+
+- Interactive choice: virtualenv / --brek / system
+- Automatically installs Ghost itself
+- Installs all GitHub dependencies even in --brek/system mode
+- Adds --break-system-packages for system Python
+- Uses rich for terminal output
+"""
+
+from __future__ import annotations
+
+import argparse
+import os
+import subprocess
+import sys
+from pathlib import Path
+from typing import List, Tuple
+
+from rich.console import Console
+from rich.panel import Panel
+from rich.prompt import Prompt
+from rich.text import Text
+
+console = Console()
+ROOT = Path(__file__).parent.resolve()
+
+# GitHub/URL dependencies
+VCS_PACKAGES = [
+ "badges @ git+https://github.com/EntySec/Badges",
+ "pex @ git+https://github.com/EntySec/Pex",
+ "colorscript @ git+https://github.com/EntySec/ColorScript",
+ "adb-shell"
+]
+
+PY_PACKAGES: List[str] = [] # any pure python packages from requirements.txt
+
+DEFAULT_VENV = ROOT / ".venv"
+
+
+def run(cmd: List[str], env: dict | None = None, check: bool = True) -> None:
+ console.log(f"[bold purple]$[/bold purple] {' '.join(cmd)}")
+ res = subprocess.run(cmd, env=env)
+ if check and res.returncode != 0:
+ raise SystemExit(f"Command failed: {' '.join(cmd)} (exit {res.returncode})")
+
+
+def ensure_virtualenv(venv_path: Path) -> Tuple[str, str]:
+ if venv_path.exists():
+ console.print(Panel(f"Using existing virtualenv: [bold]{venv_path}[/bold]", title="Virtualenv"))
+ else:
+ console.print(Panel(f"Creating virtualenv at: [bold]{venv_path}[/bold]", title="Virtualenv"))
+ run([sys.executable, "-m", "venv", str(venv_path)])
+ if os.name == "nt":
+ py = venv_path / "Scripts" / "python.exe"
+ pip = venv_path / "Scripts" / "pip.exe"
+ else:
+ py = venv_path / "bin" / "python"
+ pip = venv_path / "bin" / "pip"
+ return str(py), str(pip)
+
+
+def install_packages(py_exe: str, packages: List[str], break_system: bool = False) -> None:
+ if not packages:
+ return
+ cmd = [py_exe, "-m", "pip", "install"] + packages
+ if break_system:
+ cmd.append("--break-system-packages")
+ run(cmd)
+
+
+def install_local_package(py_exe: str, break_system: bool = False) -> None:
+ cmd = [py_exe, "-m", "pip", "install", ".", "--no-deps"]
+ if break_system:
+ cmd.append("--break-system-packages")
+ run(cmd)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description="Ghost installer (venv or --brek/system)")
+ parser.add_argument("--no-venv", action="store_true", help="Do not create/use virtualenv; install in current interpreter")
+ parser.add_argument("--venv", default=".venv", help="Virtualenv path (default: .venv)")
+ parser.add_argument("--brek", action="store_true", help="Install only python-tool packages (filter out VCS/URLs).")
+ parser.add_argument("--yes", "-y", action="store_true", help="Automatic yes for prompts")
+ args = parser.parse_args()
+
+ console.print(Panel(Text("Ghost Installer — staged installation\n\nIndex 99 → Return to Menu / Exit", justify="center"), style="purple"))
+
+ use_brek = args.brek
+ use_venv = not args.no_venv
+
+ if not (args.brek or args.no_venv):
+ choice = Prompt.ask(
+ "Choose install mode",
+ choices=["venv", "brek", "system"],
+ default="venv",
+ show_choices=True,
+ )
+ if choice == "venv":
+ use_venv = True
+ use_brek = False
+ elif choice == "brek":
+ use_venv = False
+ use_brek = True
+ else:
+ use_venv = False
+ use_brek = False
+
+ break_system_flag = False
+ if not use_venv:
+ break_system_flag = True
+ console.print(Panel("Installing into current Python interpreter (no virtualenv). --break-system-packages enabled", title="Notice", style="yellow"))
+
+ if use_venv:
+ py_exe, pip_exe = ensure_virtualenv(Path(args.venv))
+ else:
+ py_exe = sys.executable
+
+ # Upgrade pip
+ console.print("[bold]Upgrading pip in target environment...[/bold]")
+ cmd_upgrade = [py_exe, "-m", "pip", "install", "--upgrade", "pip"]
+ if break_system_flag:
+ cmd_upgrade.append("--break-system-packages")
+ run(cmd_upgrade)
+
+ # Install GitHub / VCS packages first
+ console.print(Panel(f"Installing VCS/GitHub packages ({len(VCS_PACKAGES)} items)...", title="Dependencies"))
+ install_packages(py_exe, VCS_PACKAGES, break_system_flag)
+
+ # Install local Ghost package
+ console.print(Panel("Installing Ghost main package...", style="purple"))
+ install_local_package(py_exe, break_system_flag)
+
+ console.print(Panel("[bold green]Installation completed successfully![/bold green]"))
+ console.print("You can now run: ghost")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..2aa89705
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+adb-shell>=0.1
+pex @ git+https://github.com/EntySec/Pex.git
+badges @ git+https://github.com/EntySec/Badges.git
+colorscript @ git+https://github.com/EntySec/ColorScript.git
+rich>=10.0
diff --git a/setup.py b/setup.py
index 59dad0c1..15425408 100644
--- a/setup.py
+++ b/setup.py
@@ -1,52 +1,43 @@
-"""
-MIT License
-
-Copyright (c) 2020-2024 EntySec
-
-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.
-"""
+# setup.py — minimal install_requires (keep package install from local source only)
+from pathlib import Path
from setuptools import setup, find_packages
-setup(name='ghost',
- version='8.0.0',
- description=(
- 'Ghost Framework is an Android post-exploitation framework that exploits the'
- ' Android Debug Bridge to remotely access an Android device.'
- ),
- url='http://github.com/EntySec/Ghost',
- author='EntySec',
- author_email='entysec@gmail.com',
- license='MIT',
- python_requires='>=3.7.0',
- packages=find_packages(),
- include_package_data=True,
- entry_points={
- "console_scripts": [
- "ghost = ghost:cli"
- ]
- },
- install_requires=[
- 'adb-shell',
- 'pex @ git+https://github.com/EntySec/Pex',
- 'badges @ git+https://github.com/EntySec/Badges',
- 'colorscript @ git+https://github.com/EntySec/ColorScript'
- ],
- zip_safe=False
- )
+HERE = Path(__file__).parent
+readme = (HERE / "README.md").read_text(encoding="utf-8") if (HERE / "README.md").exists() else ""
+
+setup(
+ name="ghost",
+ version="8.0.0",
+ description="Ghost Framework — Android post-exploitation framework (ADB-based).",
+ long_description=readme,
+ long_description_content_type="text/markdown",
+ url="https://github.com/EntySec/Ghost",
+ author="EntySec",
+ author_email="entysec@gmail.com",
+ license="MIT",
+ python_requires=">=3.7",
+ packages=find_packages(exclude=("tests", "docs", "examples")),
+ include_package_data=True,
+ zip_safe=False,
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "Topic :: Security",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ ],
+ # Keep runtime install_requires minimal. Put VCS/complex deps into requirements.txt
+ install_requires=[
+ "adb-shell",
+ ],
+ entry_points={
+ "console_scripts": [
+ "ghost = ghost:cli",
+ ]
+ },
+ project_urls={
+ "Source": "https://github.com/EntySec/Ghost",
+ "Issues": "https://github.com/EntySec/Ghost/issues",
+ },
+)
diff --git a/uninstall.py b/uninstall.py
new file mode 100644
index 00000000..d8f555c5
--- /dev/null
+++ b/uninstall.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+"""
+uninstall.py — Ghost cleanup/uninstall script
+
+- Deletes virtualenv if exists
+- Uninstalls Ghost and related Python packages if installed in system Python
+- Adds --break-system-packages for system Python to avoid PEP 668 errors
+- Interactive prompts with rich
+"""
+
+from __future__ import annotations
+
+import subprocess
+import sys
+from pathlib import Path
+from typing import List
+
+from rich.console import Console
+from rich.panel import Panel
+from rich.prompt import Confirm
+
+console = Console()
+ROOT = Path(__file__).parent.resolve()
+
+# List of Python packages installed by Ghost installer
+PYTHON_PACKAGES = [
+ "ghost",
+ "adb-shell",
+ "pex",
+ "badges",
+ "colorscript",
+]
+
+# Default virtualenv path
+DEFAULT_VENV = ROOT / ".venv"
+
+
+def run(cmd: List[str], check: bool = True) -> None:
+ """Run a shell command and print it."""
+ console.log(f"[bold purple]$[/bold purple] {' '.join(cmd)}")
+ res = subprocess.run(cmd)
+ if check and res.returncode != 0:
+ console.print(Panel(f"[red]Command failed: {' '.join(cmd)}[/red]"))
+
+
+def uninstall_packages(python_exe: str, break_system: bool = False) -> None:
+ """Uninstall all Ghost-related packages."""
+ for pkg in PYTHON_PACKAGES:
+ console.print(Panel(f"Uninstalling package: [bold]{pkg}[/bold]"))
+ cmd = [python_exe, "-m", "pip", "uninstall", "-y", pkg]
+ if break_system:
+ cmd.append("--break-system-packages")
+ run(cmd)
+
+
+def remove_virtualenv(venv_path: Path) -> None:
+ """Remove virtualenv folder if exists."""
+ if venv_path.exists():
+ console.print(Panel(f"Removing virtualenv: [bold]{venv_path}[/bold]"))
+ import shutil
+ shutil.rmtree(venv_path)
+ console.print(Panel(f"[green]Virtualenv removed.[/green]"))
+ else:
+ console.print(Panel("[yellow]No virtualenv found.[/yellow]"))
+
+
+def main() -> None:
+ console.print(Panel("[bold purple]Ghost Uninstaller[/bold purple]\nIndex 99 → Return to Menu / Exit"))
+
+ use_venv = False
+ if DEFAULT_VENV.exists():
+ use_venv = Confirm.ask(f"Detected virtualenv at {DEFAULT_VENV}. Remove it?", default=True)
+ if use_venv:
+ remove_virtualenv(DEFAULT_VENV)
+
+ # Ask if want to uninstall packages from system Python
+ uninstall_system = Confirm.ask("Do you want to uninstall Ghost Python packages from system interpreter?", default=False)
+ if uninstall_system:
+ python_exe = sys.executable
+ uninstall_packages(python_exe, break_system=True)
+
+ console.print(Panel("[bold green]Uninstall process completed.[/bold green]"))
+
+
+if __name__ == "__main__":
+ main()
From b56f121b58713453dbac597311771040bbdb6cd7 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 09:43:55 -0400
Subject: [PATCH 02/47] Remove install.py comments
---
install.py | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/install.py b/install.py
index 2ffb708b..640c9c6e 100755
--- a/install.py
+++ b/install.py
@@ -26,7 +26,6 @@
console = Console()
ROOT = Path(__file__).parent.resolve()
-# GitHub/URL dependencies
VCS_PACKAGES = [
"badges @ git+https://github.com/EntySec/Badges",
"pex @ git+https://github.com/EntySec/Pex",
@@ -34,7 +33,7 @@
"adb-shell"
]
-PY_PACKAGES: List[str] = [] # any pure python packages from requirements.txt
+PY_PACKAGES: List[str] = []
DEFAULT_VENV = ROOT / ".venv"
@@ -117,18 +116,15 @@ def main() -> None:
else:
py_exe = sys.executable
- # Upgrade pip
console.print("[bold]Upgrading pip in target environment...[/bold]")
cmd_upgrade = [py_exe, "-m", "pip", "install", "--upgrade", "pip"]
if break_system_flag:
cmd_upgrade.append("--break-system-packages")
run(cmd_upgrade)
- # Install GitHub / VCS packages first
console.print(Panel(f"Installing VCS/GitHub packages ({len(VCS_PACKAGES)} items)...", title="Dependencies"))
install_packages(py_exe, VCS_PACKAGES, break_system_flag)
- # Install local Ghost package
console.print(Panel("Installing Ghost main package...", style="purple"))
install_local_package(py_exe, break_system_flag)
From 9c67af9b9674d7f67c0baac31a2c98427c0ac133 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 09:44:50 -0400
Subject: [PATCH 03/47] Remove setup.py comments
---
setup.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/setup.py b/setup.py
index 15425408..02da2725 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,3 @@
-# setup.py — minimal install_requires (keep package install from local source only)
-
from pathlib import Path
from setuptools import setup, find_packages
@@ -27,7 +25,6 @@
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
],
- # Keep runtime install_requires minimal. Put VCS/complex deps into requirements.txt
install_requires=[
"adb-shell",
],
From 03e016ce3897874b24422b3a1133ce40ccdef299 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 09:45:17 -0400
Subject: [PATCH 04/47] Remove uninstall.py comments
---
uninstall.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/uninstall.py b/uninstall.py
index d8f555c5..fb5319b8 100644
--- a/uninstall.py
+++ b/uninstall.py
@@ -22,7 +22,6 @@
console = Console()
ROOT = Path(__file__).parent.resolve()
-# List of Python packages installed by Ghost installer
PYTHON_PACKAGES = [
"ghost",
"adb-shell",
@@ -31,7 +30,6 @@
"colorscript",
]
-# Default virtualenv path
DEFAULT_VENV = ROOT / ".venv"
@@ -73,7 +71,6 @@ def main() -> None:
if use_venv:
remove_virtualenv(DEFAULT_VENV)
- # Ask if want to uninstall packages from system Python
uninstall_system = Confirm.ask("Do you want to uninstall Ghost Python packages from system interpreter?", default=False)
if uninstall_system:
python_exe = sys.executable
From ef892e81c007274b7c2b19ae522c4d57eac60a6a Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 14:51:33 -0400
Subject: [PATCH 05/47] Device With Rich Ui
---
ghost/core/device.py | 68 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/ghost/core/device.py b/ghost/core/device.py
index 16fa2201..87cd54b0 100644
--- a/ghost/core/device.py
+++ b/ghost/core/device.py
@@ -32,6 +32,18 @@
from pex.fs import FS
+# Rich UI imports (visuals only — program logic is NOT modified)
+from rich.console import Console
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from rich.align import Align
+
+# Main purple theme
+_PURPLE = "#7B61FF"
+
+_console = Console()
+
class Device(Cmd, FS):
""" Subclass of ghost.core module.
@@ -63,6 +75,62 @@ def __init__(self, host: str, port: int = 5555, timeout: int = 10,
device=self
)
+ # -------------------------
+ # Rich-based Print Methods
+ # (Presentation only — do NOT change program logic)
+ # -------------------------
+ def _footer_return_to_menu(self) -> Text:
+ """Small helper to produce the 'Return to Menu' footer text."""
+ t = Text("Index 99 → Exit", style="bold")
+ t.stylize(f"bold {_PURPLE}")
+ return t
+
+ def print_process(self, message: str) -> None:
+ """Display a process/info message using Rich Panel."""
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_success(self, message: str) -> None:
+ """Display a success message using Rich Panel."""
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("SUCCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_error(self, message: str) -> None:
+ """Display an error message using Rich Panel."""
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("ERROR", style="bold white on " + _PURPLE),
+ border_style="red"
+ )
+ _console.print(panel)
+
+ def print_information(self, message: str) -> None:
+ """Display an informational message using Rich Table for clarity."""
+ table = Table.grid(padding=(0, 1))
+ table.add_column(justify="left")
+ table.add_row(Text(message))
+ panel = Panel.fit(
+ table,
+ title=Text("INFO", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_empty(self) -> None:
+ """Print an empty line/space using Rich (visual spacer)."""
+ _console.print()
+
+ # -------------------------
+ # Original methods (logic unchanged)
+ # -------------------------
def get_keys(self) -> tuple:
""" Get cryptographic keys.
From d3d508d4702c38e19f63c6591544c2c014509d5e Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 14:55:27 -0400
Subject: [PATCH 06/47] Update activity.py and battery.py
---
ghost/modules/activity.py | 25 ++++++++++++++++++++++++-
ghost/modules/battery.py | 24 ++++++++++++++++++++++++
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/ghost/modules/activity.py b/ghost/modules/activity.py
index 2ebd7348..71c24139 100644
--- a/ghost/modules/activity.py
+++ b/ghost/modules/activity.py
@@ -4,6 +4,13 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,8 +27,24 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_process(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_empty(self, message: str = ""):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "")),
+ title=Text("OUTPUT", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
def run(self, _):
self.print_process("Getting activity information...")
output = self.device.send_command("dumpsys activity")
- self.print_empty(output)
+ self.print_empty(output)
\ No newline at end of file
diff --git a/ghost/modules/battery.py b/ghost/modules/battery.py
index c81ec8d5..289e6a43 100644
--- a/ghost/modules/battery.py
+++ b/ghost/modules/battery.py
@@ -4,6 +4,14 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+# Main theme color
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,6 +28,22 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_process(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_empty(self, message: str = ""):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "No output.")),
+ title=Text("OUTPUT", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
def run(self, _):
self.print_process("Getting battery information...")
From a56e8252d5de6e4d5ac0ac535b1d90bb7f088a8e Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 14:57:43 -0400
Subject: [PATCH 07/47] Update download.py
---
ghost/modules/download.py | 42 +++++++++++++++++++++++++++++++++++++--
1 file changed, 40 insertions(+), 2 deletions(-)
diff --git a/ghost/modules/download.py b/ghost/modules/download.py
index ea030897..faa0f971 100644
--- a/ghost/modules/download.py
+++ b/ghost/modules/download.py
@@ -4,8 +4,15 @@
"""
import os
-
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+# Main theme color
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -22,5 +29,36 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_process(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_success(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("SUCCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_error(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("ERROR", style="bold white on " + _PURPLE),
+ border_style="red"
+ )
+ _console.print(panel)
+
def run(self, args):
- self.device.download(args[1], args[2])
+ remote_file, local_path = args[1], args[2]
+ self.print_process(f"Downloading {remote_file}...")
+
+ success = self.device.download(remote_file, local_path)
+ if success:
+ self.print_success(f"File saved to {local_path}")
+ else:
+ self.print_error("Download failed!")
From d9ef41a32c972caa33d2a4a8d57eb9733a183cff Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 14:58:22 -0400
Subject: [PATCH 08/47] Update keyboard.py
---
ghost/modules/keyboard.py | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/ghost/modules/keyboard.py b/ghost/modules/keyboard.py
index de044b8f..ea9804e5 100644
--- a/ghost/modules/keyboard.py
+++ b/ghost/modules/keyboard.py
@@ -6,8 +6,14 @@
import sys
import termios
import tty
-
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -24,6 +30,14 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
@staticmethod
def get_char():
fd = sys.stdin.fileno()
@@ -35,9 +49,9 @@ def get_char():
termios.tcsetattr(fd, termios.TCSADRAIN, old)
def run(self, _):
- self.print_process("Interacting with keyboard...")
- self.print_success("Interactive connection spawned!")
+ self.print_panel("PROCESS", "Interacting with keyboard...")
+ self.print_panel("SUCCESS", "Interactive connection spawned!")
+ self.print_panel("INFO", "Type text below.", color="cyan")
- self.print_information("Type text below.")
while True:
self.device.send_command(f"input text {self.get_char()}")
From 3c86bd50806cf78562d990fb2e1ef8b0d678afb3 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:05:45 -0400
Subject: [PATCH 09/47] Update list.py
---
ghost/modules/list.py | 40 ++++++++++++++++++++++++++++++++--------
1 file changed, 32 insertions(+), 8 deletions(-)
diff --git a/ghost/modules/list.py b/ghost/modules/list.py
index c6685252..fa612b72 100644
--- a/ghost/modules/list.py
+++ b/ghost/modules/list.py
@@ -4,8 +4,15 @@
"""
import datetime
-
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -22,15 +29,32 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {_PURPLE}"),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_table_rich(self, title: str, headers: tuple, rows: list):
+ table = Table(title=title, header_style=f"bold {_PURPLE}", border_style=_PURPLE)
+ for h in headers:
+ table.add_column(h, style="white", no_wrap=True)
+ for row in rows:
+ table.add_row(*[str(i) for i in row])
+ _console.print(table)
+
def run(self, args):
- output = self.device.list(args[1])
+ self.print_panel("PROCESS", f"Listing directory: {args[1]}")
+ output = self.device.list(args[1])
if output:
headers = ('Name', 'Mode', 'Size', 'Modification Time')
- data = list()
-
+ rows = []
for entry in sorted(output):
- timestamp = datetime.datetime.fromtimestamp(entry[3])
- data.append((entry[0].decode(), str(entry[1]), str(entry[2]), timestamp))
-
- self.print_table(f"Directory {args[1]}", headers, *data)
+ timestamp = datetime.datetime.fromtimestamp(entry[3]).strftime("%Y-%m-%d %H:%M:%S")
+ rows.append((entry[0].decode(), str(entry[1]), str(entry[2]), timestamp))
+ self.print_table_rich(f"Directory {args[1]}", headers, rows)
+ else:
+ self.print_panel("INFO", "No files found in this directory.")
From a1613fa6476fe39167f8dc8eae7184300ceb2759 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:06:33 -0400
Subject: [PATCH 10/47] Update network.py
---
ghost/modules/network.py | 81 ++++++++++++++++------------------------
1 file changed, 32 insertions(+), 49 deletions(-)
diff --git a/ghost/modules/network.py b/ghost/modules/network.py
index a84ee2ef..937711b7 100644
--- a/ghost/modules/network.py
+++ b/ghost/modules/network.py
@@ -4,6 +4,13 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,96 +27,72 @@ def __init__(self):
'Options': [
(
('-a', '--arp'),
- {
- 'help': "Show device ARP table.",
- 'action': 'store_true'
- }
+ {'help': "Show device ARP table.", 'action': 'store_true'}
),
(
('-i', '--ipconfig'),
- {
- 'help': "Show device IP configuration.",
- 'action': 'store_true'
- }
+ {'help': "Show device IP configuration.", 'action': 'store_true'}
),
(
('-I', '--iproute'),
- {
- 'help': "Show device route table.",
- 'action': 'store_true'
- }
+ {'help': "Show device route table.", 'action': 'store_true'}
),
(
('-l', '--locate'),
- {
- 'help': "Show device location.",
- 'action': 'store_true'
- }
+ {'help': "Show device location.", 'action': 'store_true'}
),
(
('-s', '--stats'),
- {
- 'help': "Show device network stats.",
- 'action': 'store_true'
- }
+ {'help': "Show device network stats.", 'action': 'store_true'}
),
(
('-p', '--ports'),
- {
- 'help': "Show device open ports.",
- 'action': 'store_true'
- }
+ {'help': "Show device open ports.", 'action': 'store_true'}
),
(
('-S', '--services'),
- {
- 'help': "Show device services.",
- 'action': 'store_true'
- }
+ {'help': "Show device services.", 'action': 'store_true'}
),
(
('-f', '--forwarding'),
- {
- 'help': "Show device forwarding rules.",
- 'action': 'store_true'
- }
+ {'help': "Show device forwarding rules.", 'action': 'store_true'}
)
]
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "No output.")),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, args):
outputs = []
if args.arp:
- outputs.append(
- self.device.send_command('cat /proc/net/arp'))
+ outputs.append(self.device.send_command('cat /proc/net/arp'))
if args.ipconfig:
- outputs.append(
- self.device.send_command('ip addr show'))
+ outputs.append(self.device.send_command('ip addr show'))
if args.iproute:
- outputs.append(
- self.device.send_command('ip route show'))
+ outputs.append(self.device.send_command('ip route show'))
if args.locate:
- outputs.append(
- self.device.send_command('dumpsys location'))
+ outputs.append(self.device.send_command('dumpsys location'))
if args.stats:
- outputs.append(
- self.device.send_command('cat /proc/net/netstat'))
+ outputs.append(self.device.send_command('cat /proc/net/netstat'))
if args.ports:
- outputs.append(
- self.device.send_command('busybox netstat -an'))
+ outputs.append(self.device.send_command('busybox netstat -an'))
if args.services:
- outputs.append(
- self.device.send_command('service list'))
+ outputs.append(self.device.send_command('service list'))
if args.forwarding:
- outputs.append(
- self.device.send_command('cat /proc/sys/net/ipv4/ip_forward'))
+ outputs.append(self.device.send_command('cat /proc/sys/net/ipv4/ip_forward'))
- self.print_empty('\n'.join(outputs))
+ self.print_panel("NETWORK OUTPUT", '\n'.join(outputs))
From 7c797d8b522e5323adf2d4d0fc676b10789e088f Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:07:14 -0400
Subject: [PATCH 11/47] Update openurl.py
---
ghost/modules/openurl.py | 24 +++++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/ghost/modules/openurl.py b/ghost/modules/openurl.py
index f6b4b8de..69d774b7 100644
--- a/ghost/modules/openurl.py
+++ b/ghost/modules/openurl.py
@@ -4,6 +4,13 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,8 +27,19 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, args):
- if not args[1].startswith(("http://", "https://")):
- args[1] = "http://" + args[1]
+ url = args[1]
+ if not url.startswith(("http://", "https://")):
+ url = "http://" + url
- self.device.send_command(f'am start -a android.intent.action.VIEW -d "{args[1]}"')
+ self.print_panel("PROCESS", f"Opening URL on device: {url}")
+ self.device.send_command(f'am start -a android.intent.action.VIEW -d "{url}"')
+ self.print_panel("SUCCESS", "URL opened successfully!")
From b0fee59b8437865aea1c9036200b4cf59d507ad1 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:07:51 -0400
Subject: [PATCH 12/47] Update press.py
---
ghost/modules/press.py | 24 +++++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/ghost/modules/press.py b/ghost/modules/press.py
index 5235b623..0dfbcd86 100644
--- a/ghost/modules/press.py
+++ b/ghost/modules/press.py
@@ -4,6 +4,13 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,8 +27,19 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, args):
- if int(args[1]) < 124:
- self.device.send_command(f"input keyevent {args[1]}")
+ keycode = int(args[1])
+ if keycode < 124:
+ self.print_panel("PROCESS", f"Pressing device button with keycode: {keycode}")
+ self.device.send_command(f"input keyevent {keycode}")
+ self.print_panel("SUCCESS", "Key event sent successfully!")
else:
- self.print_error("Invalid keycode!")
+ self.print_panel("ERROR", "Invalid keycode!", color="red")
From 9ab78249f68aecc13ab676fefddc6aa6ae7093bd Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:08:32 -0400
Subject: [PATCH 13/47] Update screenshot.py
---
ghost/modules/screenshot.py | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/ghost/modules/screenshot.py b/ghost/modules/screenshot.py
index 6b0b3e8b..7c39be3a 100644
--- a/ghost/modules/screenshot.py
+++ b/ghost/modules/screenshot.py
@@ -4,8 +4,14 @@
"""
import os
-
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -22,9 +28,24 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, args):
- self.print_process(f"Taking screenshot...")
+ local_path = args[1]
+ self.print_panel("PROCESS", "Taking screenshot on device...")
+
self.device.send_command("screencap /data/local/tmp/screenshot.png")
+ success = self.device.download('/data/local/tmp/screenshot.png', local_path)
+
+ if success:
+ self.print_panel("SUCCESS", f"Screenshot saved to {local_path}")
+ else:
+ self.print_panel("ERROR", "Failed to download screenshot!", color="red")
- self.device.download('/data/local/tmp/screenshot.png', args[1])
self.device.send_command("rm /data/local/tmp/screenshot.png")
From df96442211151075972e09a203486b18cef6f028 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:09:03 -0400
Subject: [PATCH 14/47] Update shell.py
---
ghost/modules/shell.py | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/ghost/modules/shell.py b/ghost/modules/shell.py
index 35227149..bad6bb45 100644
--- a/ghost/modules/shell.py
+++ b/ghost/modules/shell.py
@@ -4,6 +4,13 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,6 +27,16 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "No output.")),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, args):
- output = self.device.send_command(' '.join(args[1:]))
- self.print_empty(output)
+ command = ' '.join(args[1:])
+ self.print_panel("PROCESS", f"Executing shell command: {command}")
+ output = self.device.send_command(command)
+ self.print_panel("OUTPUT", output)
From 0f90c5b7ea2f3498598853f4b436b34c85a3847e Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:09:36 -0400
Subject: [PATCH 15/47] Update sleep.py
---
ghost/modules/sleep.py | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/ghost/modules/sleep.py b/ghost/modules/sleep.py
index 049b0547..9b8af222 100644
--- a/ghost/modules/sleep.py
+++ b/ghost/modules/sleep.py
@@ -4,6 +4,13 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,5 +27,15 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, _):
+ self.print_panel("PROCESS", "Putting device into sleep mode...")
self.device.send_command("input keyevent 26")
+ self.print_panel("SUCCESS", "Device is now in sleep mode!")
From 75e5baeca6c25487fdf8045a52277d7303a71153 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:10:12 -0400
Subject: [PATCH 16/47] Update upload.py
---
ghost/modules/upload.py | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/ghost/modules/upload.py b/ghost/modules/upload.py
index 3bbd4c6d..f28b9c0a 100644
--- a/ghost/modules/upload.py
+++ b/ghost/modules/upload.py
@@ -4,8 +4,14 @@
"""
import os
-
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -22,5 +28,20 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, args):
- self.device.upload(args[1], args[2])
+ local_file, remote_path = args[1], args[2]
+ self.print_panel("PROCESS", f"Uploading {local_file} to {remote_path}...")
+
+ success = self.device.upload(local_file, remote_path)
+ if success:
+ self.print_panel("SUCCESS", f"File uploaded to {remote_path}")
+ else:
+ self.print_panel("ERROR", "Upload failed!", color="red")
From d4c42301a866cc3a7fa3bfb00babbca770f55116 Mon Sep 17 00:00:00 2001
From: Modark
Date: Thu, 16 Oct 2025 15:10:49 -0400
Subject: [PATCH 17/47] Update wifi.py
---
ghost/modules/wifi.py | 28 ++++++++++++++++++++++------
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/ghost/modules/wifi.py b/ghost/modules/wifi.py
index 70b46198..c6dcbf3e 100644
--- a/ghost/modules/wifi.py
+++ b/ghost/modules/wifi.py
@@ -4,6 +4,13 @@
"""
from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
class ExternalCommand(Command):
@@ -20,11 +27,20 @@ def __init__(self):
'NeedsRoot': False
})
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
def run(self, args):
- if args[1] in ['on', 'off']:
- if args[1] == 'on':
- self.device.send_command("svc wifi enable")
- else:
- self.device.send_command("svc wifi disable")
+ state = args[1].lower()
+ if state in ['on', 'off']:
+ action = "enable" if state == 'on' else "disable"
+ self.print_panel("PROCESS", f"Turning WiFi {state}...")
+ self.device.send_command(f"svc wifi {action}")
+ self.print_panel("SUCCESS", f"WiFi turned {state} successfully!")
else:
- self.print_usage(self.info['Usage'])
+ self.print_panel("USAGE", f"Usage: {self.info['Usage']}", color="red")
From 72efbab8cb2c6208bb41a21cfcb0de7e06f6d2ac Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 16:23:34 -0400
Subject: [PATCH 18/47] Auto-reconnect
---
ghost/core/device.py | 255 +++++++++++++++----------------------------
1 file changed, 85 insertions(+), 170 deletions(-)
diff --git a/ghost/core/device.py b/ghost/core/device.py
index 87cd54b0..9788c664 100644
--- a/ghost/core/device.py
+++ b/ghost/core/device.py
@@ -23,271 +23,186 @@
"""
import os
-
+import time
+import threading
from badges.cmd import Cmd
-
from adb_shell.adb_device import AdbDeviceTcp
from adb_shell.auth.keygen import keygen
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
-
from pex.fs import FS
-
-# Rich UI imports (visuals only — program logic is NOT modified)
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich.align import Align
-# Main purple theme
_PURPLE = "#7B61FF"
-
_console = Console()
+_MAX_RETRIES = 5
+_RETRY_DELAY = 3
+_HEALTHCHECK_DELAY = 10
-class Device(Cmd, FS):
- """ Subclass of ghost.core module.
-
- This subclass of ghost.core module is intended for providing
- an implementation of device controller.
- """
-
- def __init__(self, host: str, port: int = 5555, timeout: int = 10,
- key_filename: str = 'key') -> None:
- """ Initialize device.
- :param str host: device host
- :param int port: device port
- :param int timeout: connection timeout
- :param str key_filename: name of the file containing key
- :return None: None
- """
+class Device(Cmd, FS):
+ """Enhanced Device class with auto-reconnect and retry mechanisms."""
+ def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename: str = 'key') -> None:
self.host = host
self.port = int(port)
-
self.key_file = key_filename
self.device = AdbDeviceTcp(self.host, self.port, default_transport_timeout_s=timeout)
+ self._reconnect_thread = None
+ self._stop_reconnect = threading.Event()
+
super().__init__(
prompt=f'(%lineghost%end: %red{self.host}%end)> ',
path=[f'{os.path.dirname(os.path.dirname(__file__))}/modules'],
device=self
)
- # -------------------------
- # Rich-based Print Methods
- # (Presentation only — do NOT change program logic)
- # -------------------------
- def _footer_return_to_menu(self) -> Text:
- """Small helper to produce the 'Return to Menu' footer text."""
- t = Text("Index 99 → Exit", style="bold")
- t.stylize(f"bold {_PURPLE}")
- return t
-
def print_process(self, message: str) -> None:
- """Display a process/info message using Rich Panel."""
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("PROCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
+ _console.print(Panel.fit(Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE))
def print_success(self, message: str) -> None:
- """Display a success message using Rich Panel."""
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("SUCCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
+ _console.print(Panel.fit(Align.left(Text(message)),
+ title=Text("SUCCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE))
def print_error(self, message: str) -> None:
- """Display an error message using Rich Panel."""
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("ERROR", style="bold white on " + _PURPLE),
- border_style="red"
- )
- _console.print(panel)
+ _console.print(Panel.fit(Align.left(Text(message)),
+ title=Text("ERROR", style="bold white on " + _PURPLE),
+ border_style="red"))
def print_information(self, message: str) -> None:
- """Display an informational message using Rich Table for clarity."""
table = Table.grid(padding=(0, 1))
table.add_column(justify="left")
table.add_row(Text(message))
- panel = Panel.fit(
- table,
- title=Text("INFO", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
+ _console.print(Panel.fit(table,
+ title=Text("INFO", style="bold white on " + _PURPLE),
+ border_style=_PURPLE))
def print_empty(self) -> None:
- """Print an empty line/space using Rich (visual spacer)."""
_console.print()
- # -------------------------
- # Original methods (logic unchanged)
- # -------------------------
def get_keys(self) -> tuple:
- """ Get cryptographic keys.
-
- :return tuple: public key, private key
- """
-
if not os.path.exists(self.key_file):
keygen(self.key_file)
+ with open(self.key_file, 'r') as f:
+ priv = f.read()
+ with open(self.key_file + '.pub', 'r') as f:
+ pub = f.read()
+ return pub, priv
- with open(self.key_file, 'r') as file:
- priv = file.read()
+ def connect(self, auto_reconnect: bool = True) -> bool:
+ """Connect device with retry and optional auto-reconnect thread."""
+ self._stop_reconnect.clear()
+ self.print_process(f"Connecting to {self.host}...")
+ keys = self.get_keys()
+ signer = PythonRSASigner(*keys)
- with open(self.key_file + '.pub', 'r') as file:
- pub = file.read()
- return pub, priv
+ for attempt in range(1, _MAX_RETRIES + 1):
+ try:
+ self.device.connect(rsa_keys=[signer], auth_timeout_s=5)
+ self.print_success(f"Connected to {self.host}!")
- def send_command(self, command: str, output: bool = True) -> str:
- """ Send command to the device.
+ if auto_reconnect:
+ self._start_auto_reconnect_thread()
+ return True
+ except Exception as e:
+ self.print_error(f"[Attempt {attempt}/{_MAX_RETRIES}] Connection failed: {e}")
+ if attempt < _MAX_RETRIES:
+ self.print_information(f"Retrying in {_RETRY_DELAY} seconds...")
+ time.sleep(_RETRY_DELAY)
+
+ self.print_error(f"Failed to connect to {self.host} after {_MAX_RETRIES} attempts!")
+ return False
- :param str command: command to send to the device
- :param bool output: return command output or not
- :return str: empty if output is False otherwise command output
- """
+ def _start_auto_reconnect_thread(self) -> None:
+ """Start background thread to monitor and auto-reconnect."""
+ if self._reconnect_thread and self._reconnect_thread.is_alive():
+ return
+ self._reconnect_thread = threading.Thread(target=self.auto_reconnect, daemon=True)
+ self._reconnect_thread.start()
+ def auto_reconnect(self) -> None:
+ """Monitor device connection and reconnect if disconnected."""
+ while not self._stop_reconnect.is_set():
+ try:
+ self.device.shell("echo ping", transport_timeout_s=3)
+ except Exception:
+ self.print_error(f"Lost connection to {self.host}, attempting reconnect...")
+ if not self.connect(auto_reconnect=False):
+ self.print_error("Auto-reconnect failed, will retry again...")
+ else:
+ self.print_success("Reconnected successfully!")
+ time.sleep(_HEALTHCHECK_DELAY)
+
+ def disconnect(self) -> None:
+ """Gracefully disconnect device and stop reconnect thread."""
+ self._stop_reconnect.set()
+ if self._reconnect_thread and self._reconnect_thread.is_alive():
+ self._reconnect_thread.join(timeout=2)
+
+ try:
+ self.device.close()
+ self.print_success(f"Disconnected from {self.host}.")
+ except Exception:
+ self.print_error("Failed to disconnect properly!")
+
+ def send_command(self, command: str, output: bool = True) -> str:
try:
cmd_output = self.device.shell(command)
except Exception:
self.print_error("Socket is not connected!")
return None
-
- if output:
- return cmd_output
-
- return ""
+ return cmd_output if output else ""
def list(self, path: str) -> list:
- """ List contents of the specified directory.
-
- :param str path: directory to list contents from
- :return list: list of directory contents
- """
-
try:
return self.device.list(path)
except Exception:
self.print_error("Failed to list directory!")
return []
- def connect(self) -> bool:
- """ Connect the specified device.
-
- :return bool: True if connection succeed
- """
-
- self.print_process(f"Connecting to {self.host}...")
-
- try:
- keys = self.get_keys()
- signer = PythonRSASigner(*keys)
-
- self.device.connect(rsa_keys=[signer], auth_timeout_s=5)
- self.print_success(f"Connected to {self.host}!")
-
- return True
-
- except Exception:
- self.print_error(f"Failed to connect to {self.host}!")
-
- return False
-
- def disconnect(self) -> None:
- """ Disconnect the specified device.
-
- :return None: None
- """
-
- self.device.close()
-
def download(self, input_file: str, output_path: str) -> bool:
- """ Download file from the specified device.
-
- :param str input_file: path of the file to download
- :param str output_path: path to output the file to
- :return bool: True if download succeed
- """
-
exists, is_dir = self.exists(output_path)
-
if exists:
if is_dir:
output_path = output_path + '/' + os.path.split(input_file)[1]
-
try:
self.print_process(f"Downloading {input_file}...")
self.device.pull(input_file, output_path)
-
- self.print_process(f"Saving to {output_path}...")
self.print_success(f"Saved to {output_path}!")
-
return True
-
except Exception:
- self.print_error(f"Remote file: {input_file}: does not exist or a directory!")
-
+ self.print_error(f"Remote file {input_file} not found or invalid!")
return False
def upload(self, input_file: str, output_path: str) -> bool:
- """ Upload file to the specified device.
-
- :param str input_file: path of the file to upload
- :param str output_path: path to output the file to
- :return bool: True if upload succeed
- """
-
if self.check_file(input_file):
try:
self.print_process(f"Uploading {input_file}...")
self.device.push(input_file, output_path)
-
- self.print_process(f"Saving to {output_path}...")
self.print_success(f"Saved to {output_path}!")
-
return True
-
except Exception:
try:
output_path = output_path + '/' + os.path.split(input_file)[1]
self.device.push(input_file, output_path)
-
except Exception:
- self.print_error(f"Remote directory: {output_path}: does not exist!")
-
+ self.print_error(f"Remote directory {output_path} does not exist!")
return False
def is_rooted(self) -> bool:
- """ Check if the specified device is rooted.
-
- :return bool: True if device is rooted
- """
-
responder = self.send_command('which su')
-
- if not responder or responder.isspace():
- return False
-
- return True
+ return bool(responder and not responder.isspace())
def interact(self) -> None:
- """ Interact with the specified device.
-
- :return None: None
- """
-
self.print_success("Interactive connection spawned!")
-
- self.print_empty()
self.print_process("Loading device modules...")
-
self.print_information(f"Modules loaded: {str(len(self.external))}")
self.loop()
From 396df9396f77440d22e58158a1ae448d0e99dbd0 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 16:31:23 -0400
Subject: [PATCH 19/47] Add Device Analyzer & Real-Time Log Viewer
---
ghost/core/device.py | 115 +++++++++++++++++++++++++++++--------------
1 file changed, 77 insertions(+), 38 deletions(-)
diff --git a/ghost/core/device.py b/ghost/core/device.py
index 9788c664..d8d364d4 100644
--- a/ghost/core/device.py
+++ b/ghost/core/device.py
@@ -30,6 +30,7 @@
from adb_shell.auth.keygen import keygen
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from pex.fs import FS
+
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
@@ -40,12 +41,12 @@
_console = Console()
_MAX_RETRIES = 5
-_RETRY_DELAY = 3
-_HEALTHCHECK_DELAY = 10
+_RETRY_DELAY = 3
+_HEALTHCHECK_DELAY = 10
class Device(Cmd, FS):
- """Enhanced Device class with auto-reconnect and retry mechanisms."""
+ """Enhanced Device class with auto-reconnect, analyzer & log viewer."""
def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename: str = 'key') -> None:
self.host = host
@@ -62,32 +63,23 @@ def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename:
device=self
)
- def print_process(self, message: str) -> None:
- _console.print(Panel.fit(Align.left(Text(message)),
- title=Text("PROCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE))
-
- def print_success(self, message: str) -> None:
- _console.print(Panel.fit(Align.left(Text(message)),
- title=Text("SUCCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE))
-
- def print_error(self, message: str) -> None:
+ # -------------------------
+ # Rich Print Helpers
+ # -------------------------
+ def print_panel(self, message: str, title: str, color: str = _PURPLE) -> None:
_console.print(Panel.fit(Align.left(Text(message)),
- title=Text("ERROR", style="bold white on " + _PURPLE),
- border_style="red"))
-
- def print_information(self, message: str) -> None:
- table = Table.grid(padding=(0, 1))
- table.add_column(justify="left")
- table.add_row(Text(message))
- _console.print(Panel.fit(table,
- title=Text("INFO", style="bold white on " + _PURPLE),
- border_style=_PURPLE))
-
- def print_empty(self) -> None:
- _console.print()
-
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color))
+
+ def print_process(self, message: str) -> None: self.print_panel(message, "PROCESS", _PURPLE)
+ def print_success(self, message: str) -> None: self.print_panel(message, "SUCCESS", "green")
+ def print_error(self, message: str) -> None: self.print_panel(message, "ERROR", "red")
+ def print_information(self, message: str) -> None: self.print_panel(message, "INFO", _PURPLE)
+ def print_empty(self): _console.print()
+
+ # -------------------------
+ # Device Keys
+ # -------------------------
def get_keys(self) -> tuple:
if not os.path.exists(self.key_file):
keygen(self.key_file)
@@ -97,8 +89,10 @@ def get_keys(self) -> tuple:
pub = f.read()
return pub, priv
+ # -------------------------
+ # Connection Management
+ # -------------------------
def connect(self, auto_reconnect: bool = True) -> bool:
- """Connect device with retry and optional auto-reconnect thread."""
self._stop_reconnect.clear()
self.print_process(f"Connecting to {self.host}...")
keys = self.get_keys()
@@ -122,14 +116,11 @@ def connect(self, auto_reconnect: bool = True) -> bool:
return False
def _start_auto_reconnect_thread(self) -> None:
- """Start background thread to monitor and auto-reconnect."""
- if self._reconnect_thread and self._reconnect_thread.is_alive():
- return
+ if self._reconnect_thread and self._reconnect_thread.is_alive(): return
self._reconnect_thread = threading.Thread(target=self.auto_reconnect, daemon=True)
self._reconnect_thread.start()
def auto_reconnect(self) -> None:
- """Monitor device connection and reconnect if disconnected."""
while not self._stop_reconnect.is_set():
try:
self.device.shell("echo ping", transport_timeout_s=3)
@@ -142,17 +133,18 @@ def auto_reconnect(self) -> None:
time.sleep(_HEALTHCHECK_DELAY)
def disconnect(self) -> None:
- """Gracefully disconnect device and stop reconnect thread."""
self._stop_reconnect.set()
if self._reconnect_thread and self._reconnect_thread.is_alive():
self._reconnect_thread.join(timeout=2)
-
try:
self.device.close()
self.print_success(f"Disconnected from {self.host}.")
except Exception:
self.print_error("Failed to disconnect properly!")
+ # -------------------------
+ # Command / File Operations
+ # -------------------------
def send_command(self, command: str, output: bool = True) -> str:
try:
cmd_output = self.device.shell(command)
@@ -171,8 +163,7 @@ def list(self, path: str) -> list:
def download(self, input_file: str, output_path: str) -> bool:
exists, is_dir = self.exists(output_path)
if exists:
- if is_dir:
- output_path = output_path + '/' + os.path.split(input_file)[1]
+ if is_dir: output_path += '/' + os.path.split(input_file)[1]
try:
self.print_process(f"Downloading {input_file}...")
self.device.pull(input_file, output_path)
@@ -191,7 +182,7 @@ def upload(self, input_file: str, output_path: str) -> bool:
return True
except Exception:
try:
- output_path = output_path + '/' + os.path.split(input_file)[1]
+ output_path += '/' + os.path.split(input_file)[1]
self.device.push(input_file, output_path)
except Exception:
self.print_error(f"Remote directory {output_path} does not exist!")
@@ -206,3 +197,51 @@ def interact(self) -> None:
self.print_process("Loading device modules...")
self.print_information(f"Modules loaded: {str(len(self.external))}")
self.loop()
+
+ # -------------------------
+ # Device Analyzer
+ # -------------------------
+ def analyze_device(self):
+ self.print_process(f"Analyzing {self.host} ...")
+ try:
+ props = {
+ "Manufacturer": self.send_command("getprop ro.product.manufacturer"),
+ "Model": self.send_command("getprop ro.product.model"),
+ "Android Version": self.send_command("getprop ro.build.version.release"),
+ "Security Patch": self.send_command("getprop ro.build.version.security_patch"),
+ "Architecture": self.send_command("getprop ro.product.cpu.abi"),
+ "Rooted": "Yes" if self.is_rooted() else "No"
+ }
+
+ table = Table(title=f"📱 Device Analysis — {self.host}", border_style=_PURPLE)
+ table.add_column("Property", style="bold white")
+ table.add_column("Value", style="dim")
+
+ for k, v in props.items(): table.add_row(k, v.strip() if v else "N/A")
+ _console.print(table)
+ self.print_success("Analysis complete!")
+ except Exception as e:
+ self.print_error(f"Analysis failed: {e}")
+
+ # -------------------------
+ # Real-Time Log Viewer
+ # -------------------------
+ def live_logcat(self):
+ self.print_information("Starting live logcat stream (Press Ctrl+C to stop)...")
+
+ def stream_logs():
+ try:
+ shell = self.device.shell("logcat -v time", decode=False)
+ for line in shell:
+ decoded = line.decode(errors="ignore").strip()
+ if " E " in decoded: _console.print(Text(decoded, style="red"))
+ elif " W " in decoded: _console.print(Text(decoded, style="yellow"))
+ else: _console.print(Text(decoded, style="dim"))
+ except KeyboardInterrupt:
+ self.print_process("Log streaming stopped by user.")
+ except Exception as e:
+ self.print_error(f"Logcat error: {e}")
+
+ t = threading.Thread(target=stream_logs)
+ t.daemon = True
+ t.start()
From e0f9a1e65bcc41f3d42977188d1f2f14b434de67 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 16:36:10 -0400
Subject: [PATCH 20/47] Add help command
---
ghost/core/console.py | 41 ++++++++++++++++++++---------------------
1 file changed, 20 insertions(+), 21 deletions(-)
diff --git a/ghost/core/console.py b/ghost/core/console.py
index 3e62ece1..b8ba6fc4 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -82,11 +82,10 @@ def __init__(self) -> None:
# Visual helpers (only)
# -------------------------
def _render_header(self) -> None:
- """Render a fancy hacker-style header with tools table."""
+ """Render a fancy hacker-style header with tools table and quick help."""
title = Text("Ghost Framework 8.0.0", style="bold white")
subtitle = Text("Developed by EntySec — https://entysec.com/", style="dim")
- # ASCII art panel (kept similar to original)
ascii_art = Text(
" .--. .-. .-.\n"
" : .--': : .' `.\n"
@@ -101,25 +100,25 @@ def _render_header(self) -> None:
border_style=PURPLE,
box=box.HEAVY,
padding=(0, 2),
- title="[bold]" + "GHOST",
+ title="[bold]GHOST",
subtitle=title,
)
- # Tools / Commands table (visual only)
- tools_table = Table.grid(expand=True)
- tools_table.add_column(ratio=1)
- tools_table.add_column(ratio=2)
-
- tools = Table(box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
- tools.add_column("Command", style="bold white", no_wrap=True)
- tools.add_column("Description", style="dim")
-
- tools.add_row("connect :[port]", "Connect to device via ADB (default port 5555)")
- tools.add_row("devices", "List connected devices")
- tools.add_row("disconnect ", "Disconnect device by ID")
- tools.add_row("interact ", "Interact with a connected device")
- tools.add_row("exit", "Quit Ghost Framework")
- tools.add_row("Index 99", "Return to Menu / Exit (UI helper)")
+ # Quick Help / Shortcuts table
+ quick_help = Table(box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
+ quick_help.add_column("Command / Alias", style="bold white", no_wrap=True)
+ quick_help.add_column("Description", style="dim")
+
+ quick_help.add_row("connect :[port]", "Connect to device via ADB (default port 5555)")
+ quick_help.add_row("devices", "List connected devices")
+ quick_help.add_row("disconnect ", "Disconnect device by ID")
+ quick_help.add_row("interact ", "Interact with a connected device")
+ quick_help.add_row("analyze ", "Run Device Analyzer")
+ quick_help.add_row("an ", "Alias for analyze")
+ quick_help.add_row("logcat ", "Start live logcat stream")
+ quick_help.add_row("lc ", "Alias for logcat")
+ quick_help.add_row("exit", "Quit Ghost Framework")
+ quick_help.add_row("Index 99", "Return to Menu / Exit (UI helper)")
right_panel = Panel(
Align.left(
@@ -131,13 +130,13 @@ def _render_header(self) -> None:
title="[bold]Info",
)
- # combine header columns
- header_columns = Columns([left, Panel(Padding(tools, (1, 2)), border_style=PURPLE), right_panel])
+ header_columns = Columns([left, Panel(Padding(quick_help, (1, 2)), border_style=PURPLE), right_panel])
self.rich.print(header_columns)
self.rich.print(Rule(style=PURPLE))
self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
self.rich.print()
+
def print_empty(self, message: str = "") -> None:
# preserve signature and behavior, but print a small spacer
self.rich.print("") # purely visual
@@ -290,7 +289,7 @@ def do_interact(self, args: list) -> None:
self.print_process(f"Interacting with device {str(device_id)}...")
self.devices[device_id]['device'].interact()
-
+
def shell(self) -> None:
""" Run console shell.
From 573020a0c63327205117530b8e870b5def431f4e Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 16:38:05 -0400
Subject: [PATCH 21/47] Remove setup.py
---
ghost/__init__.py | 35 -----
ghost/core/__init__.py | 23 ---
ghost/core/console.py | 299 ------------------------------------
ghost/core/device.py | 247 -----------------------------
ghost/modules/activity.py | 50 ------
ghost/modules/battery.py | 51 ------
ghost/modules/download.py | 64 --------
ghost/modules/keyboard.py | 57 -------
ghost/modules/list.py | 60 --------
ghost/modules/network.py | 98 ------------
ghost/modules/openurl.py | 45 ------
ghost/modules/press.py | 45 ------
ghost/modules/screenshot.py | 51 ------
ghost/modules/shell.py | 42 -----
ghost/modules/sleep.py | 41 -----
ghost/modules/upload.py | 47 ------
ghost/modules/wifi.py | 46 ------
17 files changed, 1301 deletions(-)
delete mode 100644 ghost/__init__.py
delete mode 100644 ghost/core/__init__.py
delete mode 100644 ghost/core/console.py
delete mode 100644 ghost/core/device.py
delete mode 100644 ghost/modules/activity.py
delete mode 100644 ghost/modules/battery.py
delete mode 100644 ghost/modules/download.py
delete mode 100644 ghost/modules/keyboard.py
delete mode 100644 ghost/modules/list.py
delete mode 100644 ghost/modules/network.py
delete mode 100644 ghost/modules/openurl.py
delete mode 100644 ghost/modules/press.py
delete mode 100644 ghost/modules/screenshot.py
delete mode 100644 ghost/modules/shell.py
delete mode 100644 ghost/modules/sleep.py
delete mode 100644 ghost/modules/upload.py
delete mode 100644 ghost/modules/wifi.py
diff --git a/ghost/__init__.py b/ghost/__init__.py
deleted file mode 100644
index 5cc61709..00000000
--- a/ghost/__init__.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""
-MIT License
-
-Copyright (c) 2020-2024 EntySec
-
-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.
-"""
-
-from ghost.core.console import Console
-
-
-def cli() -> None:
- """ Ghost Framework command-line interface.
-
- :return None: None
- """
-
- console = Console()
- console.shell()
diff --git a/ghost/core/__init__.py b/ghost/core/__init__.py
deleted file mode 100644
index e1ed48b4..00000000
--- a/ghost/core/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
-MIT License
-
-Copyright (c) 2020-2024 EntySec
-
-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/ghost/core/console.py b/ghost/core/console.py
deleted file mode 100644
index b8ba6fc4..00000000
--- a/ghost/core/console.py
+++ /dev/null
@@ -1,299 +0,0 @@
-"""
-MIT License
-
-Copyright (c) 2020-2024 EntySec
-
-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.
-"""
-
-from badges.cmd import Cmd
-
-from ghost.core.device import Device
-
-# --- Rich UI imports for styling (visual-only; logic untouched) ---
-from rich.console import Console as RichConsole
-from rich.panel import Panel
-from rich.table import Table
-from rich.text import Text
-from rich.align import Align
-from rich import box
-from rich.style import Style
-from rich.rule import Rule
-from rich.padding import Padding
-from rich.columns import Columns
-from rich.markdown import Markdown
-
-# Theme colors
-PURPLE = "#7B61FF"
-WHITE_ON_PURPLE = Style(color="white", bgcolor=PURPLE, bold=True)
-INFO_STYLE = Style(color=PURPLE, bold=True)
-WARN_STYLE = Style(color="yellow", bold=True)
-ERR_STYLE = Style(color="red", bold=True)
-SUCCESS_STYLE = Style(color="green", bold=True)
-
-
-class Console(Cmd):
- """ Subclass of ghost.core module.
-
- This subclass of ghost.core modules is intended for providing
- main Ghost Framework console interface.
- """
-
- def __init__(self) -> None:
- # keep original prompt/intro tokens so framework behavior is unchanged
- super().__init__(
- prompt='(%lineghost%end)> ',
- intro="""%clear%end
- .--. .-. .-.
- : .--': : .' `.
- : : _ : `-. .--. .--.`. .'
- : :; :: .. :' .; :`._-.': :
- `.__.':_;:_;`.__.'`.__.':_;
-
---=[ %bold%whiteGhost Framework 8.0.0%end
---=[ Developed by EntySec (%linehttps://entysec.com/%end)
-"""
- )
-
- # preserve devices dict & logic
- self.devices = {}
-
- # create rich console for all visual outputs
- self.rich = RichConsole()
- self._render_header()
-
- # -------------------------
- # Visual helpers (only)
- # -------------------------
- def _render_header(self) -> None:
- """Render a fancy hacker-style header with tools table and quick help."""
- title = Text("Ghost Framework 8.0.0", style="bold white")
- subtitle = Text("Developed by EntySec — https://entysec.com/", style="dim")
-
- ascii_art = Text(
- " .--. .-. .-.\n"
- " : .--': : .' `.\n"
- " : : _ : `-. .--. .--.`. .'\n"
- " : :; :: .. :' .; :`._-.': :\n"
- " `.__.':_;:_;`.__.'`.__.':_;",
- justify="center",
- )
-
- left = Panel(
- Align.center(ascii_art),
- border_style=PURPLE,
- box=box.HEAVY,
- padding=(0, 2),
- title="[bold]GHOST",
- subtitle=title,
- )
-
- # Quick Help / Shortcuts table
- quick_help = Table(box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
- quick_help.add_column("Command / Alias", style="bold white", no_wrap=True)
- quick_help.add_column("Description", style="dim")
-
- quick_help.add_row("connect :[port]", "Connect to device via ADB (default port 5555)")
- quick_help.add_row("devices", "List connected devices")
- quick_help.add_row("disconnect ", "Disconnect device by ID")
- quick_help.add_row("interact ", "Interact with a connected device")
- quick_help.add_row("analyze ", "Run Device Analyzer")
- quick_help.add_row("an ", "Alias for analyze")
- quick_help.add_row("logcat ", "Start live logcat stream")
- quick_help.add_row("lc ", "Alias for logcat")
- quick_help.add_row("exit", "Quit Ghost Framework")
- quick_help.add_row("Index 99", "Return to Menu / Exit (UI helper)")
-
- right_panel = Panel(
- Align.left(
- Text.assemble(subtitle, "\n\n", "Theme: ", (PURPLE, "Hacker • Purple"))
- ),
- border_style=PURPLE,
- box=box.ROUNDED,
- padding=(0, 1),
- title="[bold]Info",
- )
-
- header_columns = Columns([left, Panel(Padding(quick_help, (1, 2)), border_style=PURPLE), right_panel])
- self.rich.print(header_columns)
- self.rich.print(Rule(style=PURPLE))
- self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
- self.rich.print()
-
-
- def print_empty(self, message: str = "") -> None:
- # preserve signature and behavior, but print a small spacer
- self.rich.print("") # purely visual
-
- def print_information(self, message: str) -> None:
- # visually enhanced info box (preserves semantics)
- self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
-
- def print_warning(self, message: str) -> None:
- self.rich.print(Panel(Text(message), border_style="yellow", title="[bold white]WARNING", box=box.MINIMAL))
-
- def print_error(self, message: str) -> None:
- self.rich.print(Panel(Text(message), border_style="red", title="[bold white]ERROR", box=box.MINIMAL))
-
- def print_success(self, message: str) -> None:
- self.rich.print(Panel(Text(message), border_style="green", title="[bold white]SUCCESS", box=box.MINIMAL))
-
- def print_usage(self, usage: str) -> None:
- # show usage in an emphasized box
- usage_text = Text.assemble(("Usage: ", "bold"), (usage, ""))
- footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
- self.rich.print(Panel(usage_text, border_style=PURPLE, title="[bold]USAGE", subtitle=footer))
-
- def print_process(self, message: str) -> None:
- # lightweight status indicator (visual only)
- with self.rich.status(Text(message, style=INFO_STYLE), spinner="bouncingBall", spinner_style=PURPLE):
- # no blocking logic here — just visual affordance
- pass
-
- def print_table(self, title: str, columns: tuple, *rows) -> None:
- """Render a stylized table for lists like connected devices."""
- table = Table(title=title, box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
- for col in columns:
- table.add_column(str(col), header_style="bold white")
- for row in rows:
- # ensure each item is string (matching original behavior)
- table.add_row(*[str(x) for x in row])
- footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
- wrapper = Panel(Padding(table, (0, 1)), subtitle=footer, border_style=PURPLE)
- self.rich.print(wrapper)
-
- # -------------------------
- # Original command logic (UNCHANGED)
- # -------------------------
- def do_exit(self, _) -> None:
- """ Exit Ghost Framework.
-
- :return None: None
- :raises EOFError: EOF error
- """
-
- for device in list(self.devices):
- self.devices[device]['device'].disconnect()
- del self.devices[device]
-
- raise EOFError
-
- def do_connect(self, args: list) -> None:
- """ Connect device.
-
- :param list args: arguments
- :return None: None
- """
-
- if len(args) < 2:
- self.print_usage("connect :[port]")
- return
-
- address = args[1].split(':')
-
- if len(address) < 2:
- host, port = address[0], 5555
- else:
- host, port = address[0], int(address[1])
-
- device = Device(host=host, port=port)
-
- if device.connect():
- self.devices.update({
- len(self.devices): {
- 'host': host,
- 'port': str(port),
- 'device': device
- }
- })
- self.print_empty("")
-
- self.print_information(
- f"Type %greendevices%end to list all connected devices.")
- self.print_information(
- f"Type %greeninteract {str(len(self.devices) - 1)}%end "
- "to interact this device."
- )
-
- def do_devices(self, _) -> None:
- """ Show connected devices.
-
- :return None: None
- """
-
- if not self.devices:
- self.print_warning("No devices connected.")
- return
-
- devices = []
-
- for device in self.devices:
- devices.append(
- (device, self.devices[device]['host'],
- self.devices[device]['port']))
-
- self.print_table("Connected Devices", ('ID', 'Host', 'Port'), *devices)
-
- def do_disconnect(self, args: list) -> None:
- """ Disconnect device.
-
- :param list args: arguments
- :return None: None
- """
-
- if len(args) < 2:
- self.print_usage("disconnect ")
- return
-
- device_id = int(args[1])
-
- if device_id not in self.devices:
- self.print_error("Invalid device ID!")
- return
-
- self.devices[device_id]['device'].disconnect()
- self.devices.pop(device_id)
-
- def do_interact(self, args: list) -> None:
- """ Interact with device.
-
- :param list args: arguments
- :return None: None
- """
-
- if len(args) < 2:
- self.print_usage("interact ")
- return
-
- device_id = int(args[1])
-
- if device_id not in self.devices:
- self.print_error("Invalid device ID!")
- return
-
- self.print_process(f"Interacting with device {str(device_id)}...")
- self.devices[device_id]['device'].interact()
-
- def shell(self) -> None:
- """ Run console shell.
-
- :return None: None
- """
-
- self.loop()
diff --git a/ghost/core/device.py b/ghost/core/device.py
deleted file mode 100644
index d8d364d4..00000000
--- a/ghost/core/device.py
+++ /dev/null
@@ -1,247 +0,0 @@
-"""
-MIT License
-
-Copyright (c) 2020-2024 EntySec
-
-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.
-"""
-
-import os
-import time
-import threading
-from badges.cmd import Cmd
-from adb_shell.adb_device import AdbDeviceTcp
-from adb_shell.auth.keygen import keygen
-from adb_shell.auth.sign_pythonrsa import PythonRSASigner
-from pex.fs import FS
-
-from rich.console import Console
-from rich.panel import Panel
-from rich.table import Table
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-_MAX_RETRIES = 5
-_RETRY_DELAY = 3
-_HEALTHCHECK_DELAY = 10
-
-
-class Device(Cmd, FS):
- """Enhanced Device class with auto-reconnect, analyzer & log viewer."""
-
- def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename: str = 'key') -> None:
- self.host = host
- self.port = int(port)
- self.key_file = key_filename
- self.device = AdbDeviceTcp(self.host, self.port, default_transport_timeout_s=timeout)
-
- self._reconnect_thread = None
- self._stop_reconnect = threading.Event()
-
- super().__init__(
- prompt=f'(%lineghost%end: %red{self.host}%end)> ',
- path=[f'{os.path.dirname(os.path.dirname(__file__))}/modules'],
- device=self
- )
-
- # -------------------------
- # Rich Print Helpers
- # -------------------------
- def print_panel(self, message: str, title: str, color: str = _PURPLE) -> None:
- _console.print(Panel.fit(Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color))
-
- def print_process(self, message: str) -> None: self.print_panel(message, "PROCESS", _PURPLE)
- def print_success(self, message: str) -> None: self.print_panel(message, "SUCCESS", "green")
- def print_error(self, message: str) -> None: self.print_panel(message, "ERROR", "red")
- def print_information(self, message: str) -> None: self.print_panel(message, "INFO", _PURPLE)
- def print_empty(self): _console.print()
-
- # -------------------------
- # Device Keys
- # -------------------------
- def get_keys(self) -> tuple:
- if not os.path.exists(self.key_file):
- keygen(self.key_file)
- with open(self.key_file, 'r') as f:
- priv = f.read()
- with open(self.key_file + '.pub', 'r') as f:
- pub = f.read()
- return pub, priv
-
- # -------------------------
- # Connection Management
- # -------------------------
- def connect(self, auto_reconnect: bool = True) -> bool:
- self._stop_reconnect.clear()
- self.print_process(f"Connecting to {self.host}...")
- keys = self.get_keys()
- signer = PythonRSASigner(*keys)
-
- for attempt in range(1, _MAX_RETRIES + 1):
- try:
- self.device.connect(rsa_keys=[signer], auth_timeout_s=5)
- self.print_success(f"Connected to {self.host}!")
-
- if auto_reconnect:
- self._start_auto_reconnect_thread()
- return True
- except Exception as e:
- self.print_error(f"[Attempt {attempt}/{_MAX_RETRIES}] Connection failed: {e}")
- if attempt < _MAX_RETRIES:
- self.print_information(f"Retrying in {_RETRY_DELAY} seconds...")
- time.sleep(_RETRY_DELAY)
-
- self.print_error(f"Failed to connect to {self.host} after {_MAX_RETRIES} attempts!")
- return False
-
- def _start_auto_reconnect_thread(self) -> None:
- if self._reconnect_thread and self._reconnect_thread.is_alive(): return
- self._reconnect_thread = threading.Thread(target=self.auto_reconnect, daemon=True)
- self._reconnect_thread.start()
-
- def auto_reconnect(self) -> None:
- while not self._stop_reconnect.is_set():
- try:
- self.device.shell("echo ping", transport_timeout_s=3)
- except Exception:
- self.print_error(f"Lost connection to {self.host}, attempting reconnect...")
- if not self.connect(auto_reconnect=False):
- self.print_error("Auto-reconnect failed, will retry again...")
- else:
- self.print_success("Reconnected successfully!")
- time.sleep(_HEALTHCHECK_DELAY)
-
- def disconnect(self) -> None:
- self._stop_reconnect.set()
- if self._reconnect_thread and self._reconnect_thread.is_alive():
- self._reconnect_thread.join(timeout=2)
- try:
- self.device.close()
- self.print_success(f"Disconnected from {self.host}.")
- except Exception:
- self.print_error("Failed to disconnect properly!")
-
- # -------------------------
- # Command / File Operations
- # -------------------------
- def send_command(self, command: str, output: bool = True) -> str:
- try:
- cmd_output = self.device.shell(command)
- except Exception:
- self.print_error("Socket is not connected!")
- return None
- return cmd_output if output else ""
-
- def list(self, path: str) -> list:
- try:
- return self.device.list(path)
- except Exception:
- self.print_error("Failed to list directory!")
- return []
-
- def download(self, input_file: str, output_path: str) -> bool:
- exists, is_dir = self.exists(output_path)
- if exists:
- if is_dir: output_path += '/' + os.path.split(input_file)[1]
- try:
- self.print_process(f"Downloading {input_file}...")
- self.device.pull(input_file, output_path)
- self.print_success(f"Saved to {output_path}!")
- return True
- except Exception:
- self.print_error(f"Remote file {input_file} not found or invalid!")
- return False
-
- def upload(self, input_file: str, output_path: str) -> bool:
- if self.check_file(input_file):
- try:
- self.print_process(f"Uploading {input_file}...")
- self.device.push(input_file, output_path)
- self.print_success(f"Saved to {output_path}!")
- return True
- except Exception:
- try:
- output_path += '/' + os.path.split(input_file)[1]
- self.device.push(input_file, output_path)
- except Exception:
- self.print_error(f"Remote directory {output_path} does not exist!")
- return False
-
- def is_rooted(self) -> bool:
- responder = self.send_command('which su')
- return bool(responder and not responder.isspace())
-
- def interact(self) -> None:
- self.print_success("Interactive connection spawned!")
- self.print_process("Loading device modules...")
- self.print_information(f"Modules loaded: {str(len(self.external))}")
- self.loop()
-
- # -------------------------
- # Device Analyzer
- # -------------------------
- def analyze_device(self):
- self.print_process(f"Analyzing {self.host} ...")
- try:
- props = {
- "Manufacturer": self.send_command("getprop ro.product.manufacturer"),
- "Model": self.send_command("getprop ro.product.model"),
- "Android Version": self.send_command("getprop ro.build.version.release"),
- "Security Patch": self.send_command("getprop ro.build.version.security_patch"),
- "Architecture": self.send_command("getprop ro.product.cpu.abi"),
- "Rooted": "Yes" if self.is_rooted() else "No"
- }
-
- table = Table(title=f"📱 Device Analysis — {self.host}", border_style=_PURPLE)
- table.add_column("Property", style="bold white")
- table.add_column("Value", style="dim")
-
- for k, v in props.items(): table.add_row(k, v.strip() if v else "N/A")
- _console.print(table)
- self.print_success("Analysis complete!")
- except Exception as e:
- self.print_error(f"Analysis failed: {e}")
-
- # -------------------------
- # Real-Time Log Viewer
- # -------------------------
- def live_logcat(self):
- self.print_information("Starting live logcat stream (Press Ctrl+C to stop)...")
-
- def stream_logs():
- try:
- shell = self.device.shell("logcat -v time", decode=False)
- for line in shell:
- decoded = line.decode(errors="ignore").strip()
- if " E " in decoded: _console.print(Text(decoded, style="red"))
- elif " W " in decoded: _console.print(Text(decoded, style="yellow"))
- else: _console.print(Text(decoded, style="dim"))
- except KeyboardInterrupt:
- self.print_process("Log streaming stopped by user.")
- except Exception as e:
- self.print_error(f"Logcat error: {e}")
-
- t = threading.Thread(target=stream_logs)
- t.daemon = True
- t.start()
diff --git a/ghost/modules/activity.py b/ghost/modules/activity.py
deleted file mode 100644
index 71c24139..00000000
--- a/ghost/modules/activity.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "settings",
- 'Name': "activity",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Show device activity information.",
- 'Usage': "activity",
- 'MinArgs': 0,
- 'NeedsRoot': False
- })
-
- def print_process(self, message: str):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("PROCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
-
- def print_empty(self, message: str = ""):
- panel = Panel.fit(
- Align.left(Text(message if message else "")),
- title=Text("OUTPUT", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
-
- def run(self, _):
- self.print_process("Getting activity information...")
-
- output = self.device.send_command("dumpsys activity")
- self.print_empty(output)
\ No newline at end of file
diff --git a/ghost/modules/battery.py b/ghost/modules/battery.py
deleted file mode 100644
index 289e6a43..00000000
--- a/ghost/modules/battery.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-# Main theme color
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "settings",
- 'Name': "battery",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Show device battery information.",
- 'Usage': "battery",
- 'MinArgs': 0,
- 'NeedsRoot': False
- })
-
- def print_process(self, message: str):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("PROCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
-
- def print_empty(self, message: str = ""):
- panel = Panel.fit(
- Align.left(Text(message if message else "No output.")),
- title=Text("OUTPUT", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
-
- def run(self, _):
- self.print_process("Getting battery information...")
-
- output = self.device.send_command("dumpsys battery")
- self.print_empty(output)
diff --git a/ghost/modules/download.py b/ghost/modules/download.py
deleted file mode 100644
index faa0f971..00000000
--- a/ghost/modules/download.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-import os
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-# Main theme color
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "download",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Download file from device.",
- 'Usage': "download ",
- 'MinArgs': 2,
- 'NeedsRoot': False
- })
-
- def print_process(self, message: str):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("PROCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
-
- def print_success(self, message: str):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("SUCCESS", style="bold white on " + _PURPLE),
- border_style=_PURPLE
- )
- _console.print(panel)
-
- def print_error(self, message: str):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text("ERROR", style="bold white on " + _PURPLE),
- border_style="red"
- )
- _console.print(panel)
-
- def run(self, args):
- remote_file, local_path = args[1], args[2]
- self.print_process(f"Downloading {remote_file}...")
-
- success = self.device.download(remote_file, local_path)
- if success:
- self.print_success(f"File saved to {local_path}")
- else:
- self.print_error("Download failed!")
diff --git a/ghost/modules/keyboard.py b/ghost/modules/keyboard.py
deleted file mode 100644
index ea9804e5..00000000
--- a/ghost/modules/keyboard.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-import sys
-import termios
-import tty
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "keyboard",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Interact with device keyboard.",
- 'Usage': "keyboard",
- 'MinArgs': 0,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- @staticmethod
- def get_char():
- fd = sys.stdin.fileno()
- old = termios.tcgetattr(fd)
- try:
- tty.setraw(fd)
- return sys.stdin.read(1)
- finally:
- termios.tcsetattr(fd, termios.TCSADRAIN, old)
-
- def run(self, _):
- self.print_panel("PROCESS", "Interacting with keyboard...")
- self.print_panel("SUCCESS", "Interactive connection spawned!")
- self.print_panel("INFO", "Type text below.", color="cyan")
-
- while True:
- self.device.send_command(f"input text {self.get_char()}")
diff --git a/ghost/modules/list.py b/ghost/modules/list.py
deleted file mode 100644
index fa612b72..00000000
--- a/ghost/modules/list.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-import datetime
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.table import Table
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "list",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "List directory contents.",
- 'Usage': "list ",
- 'MinArgs': 1,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {_PURPLE}"),
- border_style=_PURPLE
- )
- _console.print(panel)
-
- def print_table_rich(self, title: str, headers: tuple, rows: list):
- table = Table(title=title, header_style=f"bold {_PURPLE}", border_style=_PURPLE)
- for h in headers:
- table.add_column(h, style="white", no_wrap=True)
- for row in rows:
- table.add_row(*[str(i) for i in row])
- _console.print(table)
-
- def run(self, args):
- self.print_panel("PROCESS", f"Listing directory: {args[1]}")
-
- output = self.device.list(args[1])
- if output:
- headers = ('Name', 'Mode', 'Size', 'Modification Time')
- rows = []
- for entry in sorted(output):
- timestamp = datetime.datetime.fromtimestamp(entry[3]).strftime("%Y-%m-%d %H:%M:%S")
- rows.append((entry[0].decode(), str(entry[1]), str(entry[2]), timestamp))
- self.print_table_rich(f"Directory {args[1]}", headers, rows)
- else:
- self.print_panel("INFO", "No files found in this directory.")
diff --git a/ghost/modules/network.py b/ghost/modules/network.py
deleted file mode 100644
index 937711b7..00000000
--- a/ghost/modules/network.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "network",
- 'Authors': [
- 'Loubaris - module developer'
- ],
- 'Description': "Retrieve network informations.",
- 'NeedsRoot': False,
- 'MinArgs': 1,
- 'Options': [
- (
- ('-a', '--arp'),
- {'help': "Show device ARP table.", 'action': 'store_true'}
- ),
- (
- ('-i', '--ipconfig'),
- {'help': "Show device IP configuration.", 'action': 'store_true'}
- ),
- (
- ('-I', '--iproute'),
- {'help': "Show device route table.", 'action': 'store_true'}
- ),
- (
- ('-l', '--locate'),
- {'help': "Show device location.", 'action': 'store_true'}
- ),
- (
- ('-s', '--stats'),
- {'help': "Show device network stats.", 'action': 'store_true'}
- ),
- (
- ('-p', '--ports'),
- {'help': "Show device open ports.", 'action': 'store_true'}
- ),
- (
- ('-S', '--services'),
- {'help': "Show device services.", 'action': 'store_true'}
- ),
- (
- ('-f', '--forwarding'),
- {'help': "Show device forwarding rules.", 'action': 'store_true'}
- )
- ]
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message if message else "No output.")),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, args):
- outputs = []
-
- if args.arp:
- outputs.append(self.device.send_command('cat /proc/net/arp'))
-
- if args.ipconfig:
- outputs.append(self.device.send_command('ip addr show'))
-
- if args.iproute:
- outputs.append(self.device.send_command('ip route show'))
-
- if args.locate:
- outputs.append(self.device.send_command('dumpsys location'))
-
- if args.stats:
- outputs.append(self.device.send_command('cat /proc/net/netstat'))
-
- if args.ports:
- outputs.append(self.device.send_command('busybox netstat -an'))
-
- if args.services:
- outputs.append(self.device.send_command('service list'))
-
- if args.forwarding:
- outputs.append(self.device.send_command('cat /proc/sys/net/ipv4/ip_forward'))
-
- self.print_panel("NETWORK OUTPUT", '\n'.join(outputs))
diff --git a/ghost/modules/openurl.py b/ghost/modules/openurl.py
deleted file mode 100644
index 69d774b7..00000000
--- a/ghost/modules/openurl.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "openurl",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Open URL on device.",
- 'Usage': "openurl ",
- 'MinArgs': 1,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, args):
- url = args[1]
- if not url.startswith(("http://", "https://")):
- url = "http://" + url
-
- self.print_panel("PROCESS", f"Opening URL on device: {url}")
- self.device.send_command(f'am start -a android.intent.action.VIEW -d "{url}"')
- self.print_panel("SUCCESS", "URL opened successfully!")
diff --git a/ghost/modules/press.py b/ghost/modules/press.py
deleted file mode 100644
index 0dfbcd86..00000000
--- a/ghost/modules/press.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "press",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Press device button by keycode.",
- 'Usage': "press ",
- 'MinArgs': 1,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, args):
- keycode = int(args[1])
- if keycode < 124:
- self.print_panel("PROCESS", f"Pressing device button with keycode: {keycode}")
- self.device.send_command(f"input keyevent {keycode}")
- self.print_panel("SUCCESS", "Key event sent successfully!")
- else:
- self.print_panel("ERROR", "Invalid keycode!", color="red")
diff --git a/ghost/modules/screenshot.py b/ghost/modules/screenshot.py
deleted file mode 100644
index 7c39be3a..00000000
--- a/ghost/modules/screenshot.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-import os
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "screenshot",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Take device screenshot.",
- 'Usage': "screenshot ",
- 'MinArgs': 1,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, args):
- local_path = args[1]
- self.print_panel("PROCESS", "Taking screenshot on device...")
-
- self.device.send_command("screencap /data/local/tmp/screenshot.png")
- success = self.device.download('/data/local/tmp/screenshot.png', local_path)
-
- if success:
- self.print_panel("SUCCESS", f"Screenshot saved to {local_path}")
- else:
- self.print_panel("ERROR", "Failed to download screenshot!", color="red")
-
- self.device.send_command("rm /data/local/tmp/screenshot.png")
diff --git a/ghost/modules/shell.py b/ghost/modules/shell.py
deleted file mode 100644
index bad6bb45..00000000
--- a/ghost/modules/shell.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "shell",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Execute shell command on device.",
- 'Usage': "shell ",
- 'MinArgs': 1,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message if message else "No output.")),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, args):
- command = ' '.join(args[1:])
- self.print_panel("PROCESS", f"Executing shell command: {command}")
- output = self.device.send_command(command)
- self.print_panel("OUTPUT", output)
diff --git a/ghost/modules/sleep.py b/ghost/modules/sleep.py
deleted file mode 100644
index 9b8af222..00000000
--- a/ghost/modules/sleep.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "sleep",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Put device into sleep mode.",
- 'Usage': "sleep",
- 'MinArgs': 0,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, _):
- self.print_panel("PROCESS", "Putting device into sleep mode...")
- self.device.send_command("input keyevent 26")
- self.print_panel("SUCCESS", "Device is now in sleep mode!")
diff --git a/ghost/modules/upload.py b/ghost/modules/upload.py
deleted file mode 100644
index f28b9c0a..00000000
--- a/ghost/modules/upload.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-import os
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "manage",
- 'Name': "upload",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Upload file to device.",
- 'Usage': "upload ",
- 'MinArgs': 2,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, args):
- local_file, remote_path = args[1], args[2]
- self.print_panel("PROCESS", f"Uploading {local_file} to {remote_path}...")
-
- success = self.device.upload(local_file, remote_path)
- if success:
- self.print_panel("SUCCESS", f"File uploaded to {remote_path}")
- else:
- self.print_panel("ERROR", "Upload failed!", color="red")
diff --git a/ghost/modules/wifi.py b/ghost/modules/wifi.py
deleted file mode 100644
index c6dcbf3e..00000000
--- a/ghost/modules/wifi.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""
-This module requires Ghost: https://github.com/EntySec/Ghost
-Current source: https://github.com/EntySec/Ghost
-"""
-
-from badges.cmd import Command
-from rich.console import Console
-from rich.panel import Panel
-from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
-
-
-class ExternalCommand(Command):
- def __init__(self):
- super().__init__({
- 'Category': "settings",
- 'Name': "wifi",
- 'Authors': [
- 'Ivan Nikolskiy (enty8080) - module developer'
- ],
- 'Description': "Set device wifi service state.",
- 'Usage': "wifi ",
- 'MinArgs': 1,
- 'NeedsRoot': False
- })
-
- def print_panel(self, title: str, message: str, color: str = _PURPLE):
- panel = Panel.fit(
- Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color
- )
- _console.print(panel)
-
- def run(self, args):
- state = args[1].lower()
- if state in ['on', 'off']:
- action = "enable" if state == 'on' else "disable"
- self.print_panel("PROCESS", f"Turning WiFi {state}...")
- self.device.send_command(f"svc wifi {action}")
- self.print_panel("SUCCESS", f"WiFi turned {state} successfully!")
- else:
- self.print_panel("USAGE", f"Usage: {self.info['Usage']}", color="red")
From 7c0ac5136a6558d5adc95957f3d42fabeca49828 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 16:38:27 -0400
Subject: [PATCH 22/47] Remove setup.py
---
setup.py | 40 ----------------------------------------
1 file changed, 40 deletions(-)
delete mode 100644 setup.py
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 02da2725..00000000
--- a/setup.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from pathlib import Path
-from setuptools import setup, find_packages
-
-HERE = Path(__file__).parent
-readme = (HERE / "README.md").read_text(encoding="utf-8") if (HERE / "README.md").exists() else ""
-
-setup(
- name="ghost",
- version="8.0.0",
- description="Ghost Framework — Android post-exploitation framework (ADB-based).",
- long_description=readme,
- long_description_content_type="text/markdown",
- url="https://github.com/EntySec/Ghost",
- author="EntySec",
- author_email="entysec@gmail.com",
- license="MIT",
- python_requires=">=3.7",
- packages=find_packages(exclude=("tests", "docs", "examples")),
- include_package_data=True,
- zip_safe=False,
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Intended Audience :: Developers",
- "Topic :: Security",
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python :: 3",
- ],
- install_requires=[
- "adb-shell",
- ],
- entry_points={
- "console_scripts": [
- "ghost = ghost:cli",
- ]
- },
- project_urls={
- "Source": "https://github.com/EntySec/Ghost",
- "Issues": "https://github.com/EntySec/Ghost/issues",
- },
-)
From 6fa71c791b010e1c5ed2e5c1d9cde670dd457de0 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 17:48:57 -0400
Subject: [PATCH 23/47] Remove install.py | Add main.py | Add pyproject.toml
---
install.py => main.py | 78 +++++++++++++++++--------------------------
pyproject.toml | 28 ++++++++++++++++
2 files changed, 58 insertions(+), 48 deletions(-)
rename install.py => main.py (71%)
create mode 100644 pyproject.toml
diff --git a/install.py b/main.py
similarity index 71%
rename from install.py
rename to main.py
index 640c9c6e..0cc4f0e0 100755
--- a/install.py
+++ b/main.py
@@ -1,17 +1,4 @@
-#!/usr/bin/env python3
-"""
-install.py — Ghost installer (full)
-
-- Interactive choice: virtualenv / --brek / system
-- Automatically installs Ghost itself
-- Installs all GitHub dependencies even in --brek/system mode
-- Adds --break-system-packages for system Python
-- Uses rich for terminal output
-"""
-
from __future__ import annotations
-
-import argparse
import os
import subprocess
import sys
@@ -22,6 +9,7 @@
from rich.panel import Panel
from rich.prompt import Prompt
from rich.text import Text
+import argparse
console = Console()
ROOT = Path(__file__).parent.resolve()
@@ -33,18 +21,14 @@
"adb-shell"
]
-PY_PACKAGES: List[str] = []
-
DEFAULT_VENV = ROOT / ".venv"
-
def run(cmd: List[str], env: dict | None = None, check: bool = True) -> None:
console.log(f"[bold purple]$[/bold purple] {' '.join(cmd)}")
res = subprocess.run(cmd, env=env)
if check and res.returncode != 0:
raise SystemExit(f"Command failed: {' '.join(cmd)} (exit {res.returncode})")
-
def ensure_virtualenv(venv_path: Path) -> Tuple[str, str]:
if venv_path.exists():
console.print(Panel(f"Using existing virtualenv: [bold]{venv_path}[/bold]", title="Virtualenv"))
@@ -59,7 +43,6 @@ def ensure_virtualenv(venv_path: Path) -> Tuple[str, str]:
pip = venv_path / "bin" / "pip"
return str(py), str(pip)
-
def install_packages(py_exe: str, packages: List[str], break_system: bool = False) -> None:
if not packages:
return
@@ -68,19 +51,17 @@ def install_packages(py_exe: str, packages: List[str], break_system: bool = Fals
cmd.append("--break-system-packages")
run(cmd)
-
def install_local_package(py_exe: str, break_system: bool = False) -> None:
cmd = [py_exe, "-m", "pip", "install", ".", "--no-deps"]
if break_system:
cmd.append("--break-system-packages")
run(cmd)
-
def main() -> None:
- parser = argparse.ArgumentParser(description="Ghost installer (venv or --brek/system)")
+ parser = argparse.ArgumentParser(description="Ghost installer (venv or system)")
parser.add_argument("--no-venv", action="store_true", help="Do not create/use virtualenv; install in current interpreter")
parser.add_argument("--venv", default=".venv", help="Virtualenv path (default: .venv)")
- parser.add_argument("--brek", action="store_true", help="Install only python-tool packages (filter out VCS/URLs).")
+ parser.add_argument("--brek", action="store_true", help="Install only python packages (skip VCS URLs).")
parser.add_argument("--yes", "-y", action="store_true", help="Automatic yes for prompts")
args = parser.parse_args()
@@ -89,30 +70,34 @@ def main() -> None:
use_brek = args.brek
use_venv = not args.no_venv
- if not (args.brek or args.no_venv):
- choice = Prompt.ask(
- "Choose install mode",
- choices=["venv", "brek", "system"],
- default="venv",
- show_choices=True,
- )
- if choice == "venv":
- use_venv = True
- use_brek = False
- elif choice == "brek":
- use_venv = False
- use_brek = True
- else:
+ # انتخاب خودکار در حالت بدون prompt
+ if not args.yes:
+ if not (args.brek or args.no_venv):
+ choice = Prompt.ask(
+ "Choose install mode",
+ choices=["venv", "brek", "system"],
+ default="venv",
+ show_choices=True,
+ )
+ if choice == "venv":
+ use_venv = True
+ use_brek = False
+ elif choice == "brek":
+ use_venv = False
+ use_brek = True
+ else:
+ use_venv = False
+ use_brek = False
+ else:
+ if use_brek:
use_venv = False
- use_brek = False
- break_system_flag = False
- if not use_venv:
- break_system_flag = True
- console.print(Panel("Installing into current Python interpreter (no virtualenv). --break-system-packages enabled", title="Notice", style="yellow"))
+ break_system_flag = not use_venv
+ if break_system_flag:
+ console.print(Panel("Installing into current Python interpreter. --break-system-packages enabled", title="Notice", style="yellow"))
if use_venv:
- py_exe, pip_exe = ensure_virtualenv(Path(args.venv))
+ py_exe, _ = ensure_virtualenv(Path(args.venv))
else:
py_exe = sys.executable
@@ -122,15 +107,12 @@ def main() -> None:
cmd_upgrade.append("--break-system-packages")
run(cmd_upgrade)
- console.print(Panel(f"Installing VCS/GitHub packages ({len(VCS_PACKAGES)} items)...", title="Dependencies"))
- install_packages(py_exe, VCS_PACKAGES, break_system_flag)
+ if not use_brek:
+ console.print(Panel(f"Installing VCS/GitHub packages ({len(VCS_PACKAGES)} items)...", title="Dependencies"))
+ install_packages(py_exe, VCS_PACKAGES, break_system_flag)
console.print(Panel("Installing Ghost main package...", style="purple"))
install_local_package(py_exe, break_system_flag)
console.print(Panel("[bold green]Installation completed successfully![/bold green]"))
console.print("You can now run: ghost")
-
-
-if __name__ == "__main__":
- main()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..caa2a3fd
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,28 @@
+[build-system]
+requires = ["setuptools>=65", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "ghost"
+version = "1.0.0"
+description = "Ghost — staged installation framework"
+authors = [
+ { name="Your Name", email="you@example.com" }
+]
+readme = "README.md"
+requires-python = ">=3.9"
+
+dependencies = [
+ "adb-shell"
+]
+
+optional-dependencies = {
+ vcs = [
+ "badges @ git+https://github.com/EntySec/Badges",
+ "pex @ git+https://github.com/EntySec/Pex",
+ "colorscript @ git+https://github.com/EntySec/ColorScript"
+ ]
+}
+
+[project.scripts]
+ghost = "ghost.main:main"
From b455950fa851e4612f1d064d307d0c58f630e2e2 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 17:51:27 -0400
Subject: [PATCH 24/47] Revert "Remove setup.py"
This reverts commit 573020a0c63327205117530b8e870b5def431f4e.
---
ghost/__init__.py | 35 +++++
ghost/core/__init__.py | 23 +++
ghost/core/console.py | 299 ++++++++++++++++++++++++++++++++++++
ghost/core/device.py | 247 +++++++++++++++++++++++++++++
ghost/modules/activity.py | 50 ++++++
ghost/modules/battery.py | 51 ++++++
ghost/modules/download.py | 64 ++++++++
ghost/modules/keyboard.py | 57 +++++++
ghost/modules/list.py | 60 ++++++++
ghost/modules/network.py | 98 ++++++++++++
ghost/modules/openurl.py | 45 ++++++
ghost/modules/press.py | 45 ++++++
ghost/modules/screenshot.py | 51 ++++++
ghost/modules/shell.py | 42 +++++
ghost/modules/sleep.py | 41 +++++
ghost/modules/upload.py | 47 ++++++
ghost/modules/wifi.py | 46 ++++++
17 files changed, 1301 insertions(+)
create mode 100644 ghost/__init__.py
create mode 100644 ghost/core/__init__.py
create mode 100644 ghost/core/console.py
create mode 100644 ghost/core/device.py
create mode 100644 ghost/modules/activity.py
create mode 100644 ghost/modules/battery.py
create mode 100644 ghost/modules/download.py
create mode 100644 ghost/modules/keyboard.py
create mode 100644 ghost/modules/list.py
create mode 100644 ghost/modules/network.py
create mode 100644 ghost/modules/openurl.py
create mode 100644 ghost/modules/press.py
create mode 100644 ghost/modules/screenshot.py
create mode 100644 ghost/modules/shell.py
create mode 100644 ghost/modules/sleep.py
create mode 100644 ghost/modules/upload.py
create mode 100644 ghost/modules/wifi.py
diff --git a/ghost/__init__.py b/ghost/__init__.py
new file mode 100644
index 00000000..5cc61709
--- /dev/null
+++ b/ghost/__init__.py
@@ -0,0 +1,35 @@
+"""
+MIT License
+
+Copyright (c) 2020-2024 EntySec
+
+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.
+"""
+
+from ghost.core.console import Console
+
+
+def cli() -> None:
+ """ Ghost Framework command-line interface.
+
+ :return None: None
+ """
+
+ console = Console()
+ console.shell()
diff --git a/ghost/core/__init__.py b/ghost/core/__init__.py
new file mode 100644
index 00000000..e1ed48b4
--- /dev/null
+++ b/ghost/core/__init__.py
@@ -0,0 +1,23 @@
+"""
+MIT License
+
+Copyright (c) 2020-2024 EntySec
+
+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/ghost/core/console.py b/ghost/core/console.py
new file mode 100644
index 00000000..b8ba6fc4
--- /dev/null
+++ b/ghost/core/console.py
@@ -0,0 +1,299 @@
+"""
+MIT License
+
+Copyright (c) 2020-2024 EntySec
+
+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.
+"""
+
+from badges.cmd import Cmd
+
+from ghost.core.device import Device
+
+# --- Rich UI imports for styling (visual-only; logic untouched) ---
+from rich.console import Console as RichConsole
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from rich.align import Align
+from rich import box
+from rich.style import Style
+from rich.rule import Rule
+from rich.padding import Padding
+from rich.columns import Columns
+from rich.markdown import Markdown
+
+# Theme colors
+PURPLE = "#7B61FF"
+WHITE_ON_PURPLE = Style(color="white", bgcolor=PURPLE, bold=True)
+INFO_STYLE = Style(color=PURPLE, bold=True)
+WARN_STYLE = Style(color="yellow", bold=True)
+ERR_STYLE = Style(color="red", bold=True)
+SUCCESS_STYLE = Style(color="green", bold=True)
+
+
+class Console(Cmd):
+ """ Subclass of ghost.core module.
+
+ This subclass of ghost.core modules is intended for providing
+ main Ghost Framework console interface.
+ """
+
+ def __init__(self) -> None:
+ # keep original prompt/intro tokens so framework behavior is unchanged
+ super().__init__(
+ prompt='(%lineghost%end)> ',
+ intro="""%clear%end
+ .--. .-. .-.
+ : .--': : .' `.
+ : : _ : `-. .--. .--.`. .'
+ : :; :: .. :' .; :`._-.': :
+ `.__.':_;:_;`.__.'`.__.':_;
+
+--=[ %bold%whiteGhost Framework 8.0.0%end
+--=[ Developed by EntySec (%linehttps://entysec.com/%end)
+"""
+ )
+
+ # preserve devices dict & logic
+ self.devices = {}
+
+ # create rich console for all visual outputs
+ self.rich = RichConsole()
+ self._render_header()
+
+ # -------------------------
+ # Visual helpers (only)
+ # -------------------------
+ def _render_header(self) -> None:
+ """Render a fancy hacker-style header with tools table and quick help."""
+ title = Text("Ghost Framework 8.0.0", style="bold white")
+ subtitle = Text("Developed by EntySec — https://entysec.com/", style="dim")
+
+ ascii_art = Text(
+ " .--. .-. .-.\n"
+ " : .--': : .' `.\n"
+ " : : _ : `-. .--. .--.`. .'\n"
+ " : :; :: .. :' .; :`._-.': :\n"
+ " `.__.':_;:_;`.__.'`.__.':_;",
+ justify="center",
+ )
+
+ left = Panel(
+ Align.center(ascii_art),
+ border_style=PURPLE,
+ box=box.HEAVY,
+ padding=(0, 2),
+ title="[bold]GHOST",
+ subtitle=title,
+ )
+
+ # Quick Help / Shortcuts table
+ quick_help = Table(box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
+ quick_help.add_column("Command / Alias", style="bold white", no_wrap=True)
+ quick_help.add_column("Description", style="dim")
+
+ quick_help.add_row("connect :[port]", "Connect to device via ADB (default port 5555)")
+ quick_help.add_row("devices", "List connected devices")
+ quick_help.add_row("disconnect ", "Disconnect device by ID")
+ quick_help.add_row("interact ", "Interact with a connected device")
+ quick_help.add_row("analyze ", "Run Device Analyzer")
+ quick_help.add_row("an ", "Alias for analyze")
+ quick_help.add_row("logcat ", "Start live logcat stream")
+ quick_help.add_row("lc ", "Alias for logcat")
+ quick_help.add_row("exit", "Quit Ghost Framework")
+ quick_help.add_row("Index 99", "Return to Menu / Exit (UI helper)")
+
+ right_panel = Panel(
+ Align.left(
+ Text.assemble(subtitle, "\n\n", "Theme: ", (PURPLE, "Hacker • Purple"))
+ ),
+ border_style=PURPLE,
+ box=box.ROUNDED,
+ padding=(0, 1),
+ title="[bold]Info",
+ )
+
+ header_columns = Columns([left, Panel(Padding(quick_help, (1, 2)), border_style=PURPLE), right_panel])
+ self.rich.print(header_columns)
+ self.rich.print(Rule(style=PURPLE))
+ self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
+ self.rich.print()
+
+
+ def print_empty(self, message: str = "") -> None:
+ # preserve signature and behavior, but print a small spacer
+ self.rich.print("") # purely visual
+
+ def print_information(self, message: str) -> None:
+ # visually enhanced info box (preserves semantics)
+ self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
+
+ def print_warning(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="yellow", title="[bold white]WARNING", box=box.MINIMAL))
+
+ def print_error(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="red", title="[bold white]ERROR", box=box.MINIMAL))
+
+ def print_success(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="green", title="[bold white]SUCCESS", box=box.MINIMAL))
+
+ def print_usage(self, usage: str) -> None:
+ # show usage in an emphasized box
+ usage_text = Text.assemble(("Usage: ", "bold"), (usage, ""))
+ footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
+ self.rich.print(Panel(usage_text, border_style=PURPLE, title="[bold]USAGE", subtitle=footer))
+
+ def print_process(self, message: str) -> None:
+ # lightweight status indicator (visual only)
+ with self.rich.status(Text(message, style=INFO_STYLE), spinner="bouncingBall", spinner_style=PURPLE):
+ # no blocking logic here — just visual affordance
+ pass
+
+ def print_table(self, title: str, columns: tuple, *rows) -> None:
+ """Render a stylized table for lists like connected devices."""
+ table = Table(title=title, box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
+ for col in columns:
+ table.add_column(str(col), header_style="bold white")
+ for row in rows:
+ # ensure each item is string (matching original behavior)
+ table.add_row(*[str(x) for x in row])
+ footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
+ wrapper = Panel(Padding(table, (0, 1)), subtitle=footer, border_style=PURPLE)
+ self.rich.print(wrapper)
+
+ # -------------------------
+ # Original command logic (UNCHANGED)
+ # -------------------------
+ def do_exit(self, _) -> None:
+ """ Exit Ghost Framework.
+
+ :return None: None
+ :raises EOFError: EOF error
+ """
+
+ for device in list(self.devices):
+ self.devices[device]['device'].disconnect()
+ del self.devices[device]
+
+ raise EOFError
+
+ def do_connect(self, args: list) -> None:
+ """ Connect device.
+
+ :param list args: arguments
+ :return None: None
+ """
+
+ if len(args) < 2:
+ self.print_usage("connect :[port]")
+ return
+
+ address = args[1].split(':')
+
+ if len(address) < 2:
+ host, port = address[0], 5555
+ else:
+ host, port = address[0], int(address[1])
+
+ device = Device(host=host, port=port)
+
+ if device.connect():
+ self.devices.update({
+ len(self.devices): {
+ 'host': host,
+ 'port': str(port),
+ 'device': device
+ }
+ })
+ self.print_empty("")
+
+ self.print_information(
+ f"Type %greendevices%end to list all connected devices.")
+ self.print_information(
+ f"Type %greeninteract {str(len(self.devices) - 1)}%end "
+ "to interact this device."
+ )
+
+ def do_devices(self, _) -> None:
+ """ Show connected devices.
+
+ :return None: None
+ """
+
+ if not self.devices:
+ self.print_warning("No devices connected.")
+ return
+
+ devices = []
+
+ for device in self.devices:
+ devices.append(
+ (device, self.devices[device]['host'],
+ self.devices[device]['port']))
+
+ self.print_table("Connected Devices", ('ID', 'Host', 'Port'), *devices)
+
+ def do_disconnect(self, args: list) -> None:
+ """ Disconnect device.
+
+ :param list args: arguments
+ :return None: None
+ """
+
+ if len(args) < 2:
+ self.print_usage("disconnect ")
+ return
+
+ device_id = int(args[1])
+
+ if device_id not in self.devices:
+ self.print_error("Invalid device ID!")
+ return
+
+ self.devices[device_id]['device'].disconnect()
+ self.devices.pop(device_id)
+
+ def do_interact(self, args: list) -> None:
+ """ Interact with device.
+
+ :param list args: arguments
+ :return None: None
+ """
+
+ if len(args) < 2:
+ self.print_usage("interact ")
+ return
+
+ device_id = int(args[1])
+
+ if device_id not in self.devices:
+ self.print_error("Invalid device ID!")
+ return
+
+ self.print_process(f"Interacting with device {str(device_id)}...")
+ self.devices[device_id]['device'].interact()
+
+ def shell(self) -> None:
+ """ Run console shell.
+
+ :return None: None
+ """
+
+ self.loop()
diff --git a/ghost/core/device.py b/ghost/core/device.py
new file mode 100644
index 00000000..d8d364d4
--- /dev/null
+++ b/ghost/core/device.py
@@ -0,0 +1,247 @@
+"""
+MIT License
+
+Copyright (c) 2020-2024 EntySec
+
+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.
+"""
+
+import os
+import time
+import threading
+from badges.cmd import Cmd
+from adb_shell.adb_device import AdbDeviceTcp
+from adb_shell.auth.keygen import keygen
+from adb_shell.auth.sign_pythonrsa import PythonRSASigner
+from pex.fs import FS
+
+from rich.console import Console
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+_MAX_RETRIES = 5
+_RETRY_DELAY = 3
+_HEALTHCHECK_DELAY = 10
+
+
+class Device(Cmd, FS):
+ """Enhanced Device class with auto-reconnect, analyzer & log viewer."""
+
+ def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename: str = 'key') -> None:
+ self.host = host
+ self.port = int(port)
+ self.key_file = key_filename
+ self.device = AdbDeviceTcp(self.host, self.port, default_transport_timeout_s=timeout)
+
+ self._reconnect_thread = None
+ self._stop_reconnect = threading.Event()
+
+ super().__init__(
+ prompt=f'(%lineghost%end: %red{self.host}%end)> ',
+ path=[f'{os.path.dirname(os.path.dirname(__file__))}/modules'],
+ device=self
+ )
+
+ # -------------------------
+ # Rich Print Helpers
+ # -------------------------
+ def print_panel(self, message: str, title: str, color: str = _PURPLE) -> None:
+ _console.print(Panel.fit(Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color))
+
+ def print_process(self, message: str) -> None: self.print_panel(message, "PROCESS", _PURPLE)
+ def print_success(self, message: str) -> None: self.print_panel(message, "SUCCESS", "green")
+ def print_error(self, message: str) -> None: self.print_panel(message, "ERROR", "red")
+ def print_information(self, message: str) -> None: self.print_panel(message, "INFO", _PURPLE)
+ def print_empty(self): _console.print()
+
+ # -------------------------
+ # Device Keys
+ # -------------------------
+ def get_keys(self) -> tuple:
+ if not os.path.exists(self.key_file):
+ keygen(self.key_file)
+ with open(self.key_file, 'r') as f:
+ priv = f.read()
+ with open(self.key_file + '.pub', 'r') as f:
+ pub = f.read()
+ return pub, priv
+
+ # -------------------------
+ # Connection Management
+ # -------------------------
+ def connect(self, auto_reconnect: bool = True) -> bool:
+ self._stop_reconnect.clear()
+ self.print_process(f"Connecting to {self.host}...")
+ keys = self.get_keys()
+ signer = PythonRSASigner(*keys)
+
+ for attempt in range(1, _MAX_RETRIES + 1):
+ try:
+ self.device.connect(rsa_keys=[signer], auth_timeout_s=5)
+ self.print_success(f"Connected to {self.host}!")
+
+ if auto_reconnect:
+ self._start_auto_reconnect_thread()
+ return True
+ except Exception as e:
+ self.print_error(f"[Attempt {attempt}/{_MAX_RETRIES}] Connection failed: {e}")
+ if attempt < _MAX_RETRIES:
+ self.print_information(f"Retrying in {_RETRY_DELAY} seconds...")
+ time.sleep(_RETRY_DELAY)
+
+ self.print_error(f"Failed to connect to {self.host} after {_MAX_RETRIES} attempts!")
+ return False
+
+ def _start_auto_reconnect_thread(self) -> None:
+ if self._reconnect_thread and self._reconnect_thread.is_alive(): return
+ self._reconnect_thread = threading.Thread(target=self.auto_reconnect, daemon=True)
+ self._reconnect_thread.start()
+
+ def auto_reconnect(self) -> None:
+ while not self._stop_reconnect.is_set():
+ try:
+ self.device.shell("echo ping", transport_timeout_s=3)
+ except Exception:
+ self.print_error(f"Lost connection to {self.host}, attempting reconnect...")
+ if not self.connect(auto_reconnect=False):
+ self.print_error("Auto-reconnect failed, will retry again...")
+ else:
+ self.print_success("Reconnected successfully!")
+ time.sleep(_HEALTHCHECK_DELAY)
+
+ def disconnect(self) -> None:
+ self._stop_reconnect.set()
+ if self._reconnect_thread and self._reconnect_thread.is_alive():
+ self._reconnect_thread.join(timeout=2)
+ try:
+ self.device.close()
+ self.print_success(f"Disconnected from {self.host}.")
+ except Exception:
+ self.print_error("Failed to disconnect properly!")
+
+ # -------------------------
+ # Command / File Operations
+ # -------------------------
+ def send_command(self, command: str, output: bool = True) -> str:
+ try:
+ cmd_output = self.device.shell(command)
+ except Exception:
+ self.print_error("Socket is not connected!")
+ return None
+ return cmd_output if output else ""
+
+ def list(self, path: str) -> list:
+ try:
+ return self.device.list(path)
+ except Exception:
+ self.print_error("Failed to list directory!")
+ return []
+
+ def download(self, input_file: str, output_path: str) -> bool:
+ exists, is_dir = self.exists(output_path)
+ if exists:
+ if is_dir: output_path += '/' + os.path.split(input_file)[1]
+ try:
+ self.print_process(f"Downloading {input_file}...")
+ self.device.pull(input_file, output_path)
+ self.print_success(f"Saved to {output_path}!")
+ return True
+ except Exception:
+ self.print_error(f"Remote file {input_file} not found or invalid!")
+ return False
+
+ def upload(self, input_file: str, output_path: str) -> bool:
+ if self.check_file(input_file):
+ try:
+ self.print_process(f"Uploading {input_file}...")
+ self.device.push(input_file, output_path)
+ self.print_success(f"Saved to {output_path}!")
+ return True
+ except Exception:
+ try:
+ output_path += '/' + os.path.split(input_file)[1]
+ self.device.push(input_file, output_path)
+ except Exception:
+ self.print_error(f"Remote directory {output_path} does not exist!")
+ return False
+
+ def is_rooted(self) -> bool:
+ responder = self.send_command('which su')
+ return bool(responder and not responder.isspace())
+
+ def interact(self) -> None:
+ self.print_success("Interactive connection spawned!")
+ self.print_process("Loading device modules...")
+ self.print_information(f"Modules loaded: {str(len(self.external))}")
+ self.loop()
+
+ # -------------------------
+ # Device Analyzer
+ # -------------------------
+ def analyze_device(self):
+ self.print_process(f"Analyzing {self.host} ...")
+ try:
+ props = {
+ "Manufacturer": self.send_command("getprop ro.product.manufacturer"),
+ "Model": self.send_command("getprop ro.product.model"),
+ "Android Version": self.send_command("getprop ro.build.version.release"),
+ "Security Patch": self.send_command("getprop ro.build.version.security_patch"),
+ "Architecture": self.send_command("getprop ro.product.cpu.abi"),
+ "Rooted": "Yes" if self.is_rooted() else "No"
+ }
+
+ table = Table(title=f"📱 Device Analysis — {self.host}", border_style=_PURPLE)
+ table.add_column("Property", style="bold white")
+ table.add_column("Value", style="dim")
+
+ for k, v in props.items(): table.add_row(k, v.strip() if v else "N/A")
+ _console.print(table)
+ self.print_success("Analysis complete!")
+ except Exception as e:
+ self.print_error(f"Analysis failed: {e}")
+
+ # -------------------------
+ # Real-Time Log Viewer
+ # -------------------------
+ def live_logcat(self):
+ self.print_information("Starting live logcat stream (Press Ctrl+C to stop)...")
+
+ def stream_logs():
+ try:
+ shell = self.device.shell("logcat -v time", decode=False)
+ for line in shell:
+ decoded = line.decode(errors="ignore").strip()
+ if " E " in decoded: _console.print(Text(decoded, style="red"))
+ elif " W " in decoded: _console.print(Text(decoded, style="yellow"))
+ else: _console.print(Text(decoded, style="dim"))
+ except KeyboardInterrupt:
+ self.print_process("Log streaming stopped by user.")
+ except Exception as e:
+ self.print_error(f"Logcat error: {e}")
+
+ t = threading.Thread(target=stream_logs)
+ t.daemon = True
+ t.start()
diff --git a/ghost/modules/activity.py b/ghost/modules/activity.py
new file mode 100644
index 00000000..71c24139
--- /dev/null
+++ b/ghost/modules/activity.py
@@ -0,0 +1,50 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "settings",
+ 'Name': "activity",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Show device activity information.",
+ 'Usage': "activity",
+ 'MinArgs': 0,
+ 'NeedsRoot': False
+ })
+
+ def print_process(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_empty(self, message: str = ""):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "")),
+ title=Text("OUTPUT", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def run(self, _):
+ self.print_process("Getting activity information...")
+
+ output = self.device.send_command("dumpsys activity")
+ self.print_empty(output)
\ No newline at end of file
diff --git a/ghost/modules/battery.py b/ghost/modules/battery.py
new file mode 100644
index 00000000..289e6a43
--- /dev/null
+++ b/ghost/modules/battery.py
@@ -0,0 +1,51 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+# Main theme color
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "settings",
+ 'Name': "battery",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Show device battery information.",
+ 'Usage': "battery",
+ 'MinArgs': 0,
+ 'NeedsRoot': False
+ })
+
+ def print_process(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_empty(self, message: str = ""):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "No output.")),
+ title=Text("OUTPUT", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def run(self, _):
+ self.print_process("Getting battery information...")
+
+ output = self.device.send_command("dumpsys battery")
+ self.print_empty(output)
diff --git a/ghost/modules/download.py b/ghost/modules/download.py
new file mode 100644
index 00000000..faa0f971
--- /dev/null
+++ b/ghost/modules/download.py
@@ -0,0 +1,64 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+import os
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+# Main theme color
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "download",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Download file from device.",
+ 'Usage': "download ",
+ 'MinArgs': 2,
+ 'NeedsRoot': False
+ })
+
+ def print_process(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("PROCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_success(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("SUCCESS", style="bold white on " + _PURPLE),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_error(self, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text("ERROR", style="bold white on " + _PURPLE),
+ border_style="red"
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ remote_file, local_path = args[1], args[2]
+ self.print_process(f"Downloading {remote_file}...")
+
+ success = self.device.download(remote_file, local_path)
+ if success:
+ self.print_success(f"File saved to {local_path}")
+ else:
+ self.print_error("Download failed!")
diff --git a/ghost/modules/keyboard.py b/ghost/modules/keyboard.py
new file mode 100644
index 00000000..ea9804e5
--- /dev/null
+++ b/ghost/modules/keyboard.py
@@ -0,0 +1,57 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+import sys
+import termios
+import tty
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "keyboard",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Interact with device keyboard.",
+ 'Usage': "keyboard",
+ 'MinArgs': 0,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ @staticmethod
+ def get_char():
+ fd = sys.stdin.fileno()
+ old = termios.tcgetattr(fd)
+ try:
+ tty.setraw(fd)
+ return sys.stdin.read(1)
+ finally:
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
+
+ def run(self, _):
+ self.print_panel("PROCESS", "Interacting with keyboard...")
+ self.print_panel("SUCCESS", "Interactive connection spawned!")
+ self.print_panel("INFO", "Type text below.", color="cyan")
+
+ while True:
+ self.device.send_command(f"input text {self.get_char()}")
diff --git a/ghost/modules/list.py b/ghost/modules/list.py
new file mode 100644
index 00000000..fa612b72
--- /dev/null
+++ b/ghost/modules/list.py
@@ -0,0 +1,60 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+import datetime
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.table import Table
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "list",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "List directory contents.",
+ 'Usage': "list ",
+ 'MinArgs': 1,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {_PURPLE}"),
+ border_style=_PURPLE
+ )
+ _console.print(panel)
+
+ def print_table_rich(self, title: str, headers: tuple, rows: list):
+ table = Table(title=title, header_style=f"bold {_PURPLE}", border_style=_PURPLE)
+ for h in headers:
+ table.add_column(h, style="white", no_wrap=True)
+ for row in rows:
+ table.add_row(*[str(i) for i in row])
+ _console.print(table)
+
+ def run(self, args):
+ self.print_panel("PROCESS", f"Listing directory: {args[1]}")
+
+ output = self.device.list(args[1])
+ if output:
+ headers = ('Name', 'Mode', 'Size', 'Modification Time')
+ rows = []
+ for entry in sorted(output):
+ timestamp = datetime.datetime.fromtimestamp(entry[3]).strftime("%Y-%m-%d %H:%M:%S")
+ rows.append((entry[0].decode(), str(entry[1]), str(entry[2]), timestamp))
+ self.print_table_rich(f"Directory {args[1]}", headers, rows)
+ else:
+ self.print_panel("INFO", "No files found in this directory.")
diff --git a/ghost/modules/network.py b/ghost/modules/network.py
new file mode 100644
index 00000000..937711b7
--- /dev/null
+++ b/ghost/modules/network.py
@@ -0,0 +1,98 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "network",
+ 'Authors': [
+ 'Loubaris - module developer'
+ ],
+ 'Description': "Retrieve network informations.",
+ 'NeedsRoot': False,
+ 'MinArgs': 1,
+ 'Options': [
+ (
+ ('-a', '--arp'),
+ {'help': "Show device ARP table.", 'action': 'store_true'}
+ ),
+ (
+ ('-i', '--ipconfig'),
+ {'help': "Show device IP configuration.", 'action': 'store_true'}
+ ),
+ (
+ ('-I', '--iproute'),
+ {'help': "Show device route table.", 'action': 'store_true'}
+ ),
+ (
+ ('-l', '--locate'),
+ {'help': "Show device location.", 'action': 'store_true'}
+ ),
+ (
+ ('-s', '--stats'),
+ {'help': "Show device network stats.", 'action': 'store_true'}
+ ),
+ (
+ ('-p', '--ports'),
+ {'help': "Show device open ports.", 'action': 'store_true'}
+ ),
+ (
+ ('-S', '--services'),
+ {'help': "Show device services.", 'action': 'store_true'}
+ ),
+ (
+ ('-f', '--forwarding'),
+ {'help': "Show device forwarding rules.", 'action': 'store_true'}
+ )
+ ]
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "No output.")),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ outputs = []
+
+ if args.arp:
+ outputs.append(self.device.send_command('cat /proc/net/arp'))
+
+ if args.ipconfig:
+ outputs.append(self.device.send_command('ip addr show'))
+
+ if args.iproute:
+ outputs.append(self.device.send_command('ip route show'))
+
+ if args.locate:
+ outputs.append(self.device.send_command('dumpsys location'))
+
+ if args.stats:
+ outputs.append(self.device.send_command('cat /proc/net/netstat'))
+
+ if args.ports:
+ outputs.append(self.device.send_command('busybox netstat -an'))
+
+ if args.services:
+ outputs.append(self.device.send_command('service list'))
+
+ if args.forwarding:
+ outputs.append(self.device.send_command('cat /proc/sys/net/ipv4/ip_forward'))
+
+ self.print_panel("NETWORK OUTPUT", '\n'.join(outputs))
diff --git a/ghost/modules/openurl.py b/ghost/modules/openurl.py
new file mode 100644
index 00000000..69d774b7
--- /dev/null
+++ b/ghost/modules/openurl.py
@@ -0,0 +1,45 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "openurl",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Open URL on device.",
+ 'Usage': "openurl ",
+ 'MinArgs': 1,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ url = args[1]
+ if not url.startswith(("http://", "https://")):
+ url = "http://" + url
+
+ self.print_panel("PROCESS", f"Opening URL on device: {url}")
+ self.device.send_command(f'am start -a android.intent.action.VIEW -d "{url}"')
+ self.print_panel("SUCCESS", "URL opened successfully!")
diff --git a/ghost/modules/press.py b/ghost/modules/press.py
new file mode 100644
index 00000000..0dfbcd86
--- /dev/null
+++ b/ghost/modules/press.py
@@ -0,0 +1,45 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "press",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Press device button by keycode.",
+ 'Usage': "press ",
+ 'MinArgs': 1,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ keycode = int(args[1])
+ if keycode < 124:
+ self.print_panel("PROCESS", f"Pressing device button with keycode: {keycode}")
+ self.device.send_command(f"input keyevent {keycode}")
+ self.print_panel("SUCCESS", "Key event sent successfully!")
+ else:
+ self.print_panel("ERROR", "Invalid keycode!", color="red")
diff --git a/ghost/modules/screenshot.py b/ghost/modules/screenshot.py
new file mode 100644
index 00000000..7c39be3a
--- /dev/null
+++ b/ghost/modules/screenshot.py
@@ -0,0 +1,51 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+import os
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "screenshot",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Take device screenshot.",
+ 'Usage': "screenshot ",
+ 'MinArgs': 1,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ local_path = args[1]
+ self.print_panel("PROCESS", "Taking screenshot on device...")
+
+ self.device.send_command("screencap /data/local/tmp/screenshot.png")
+ success = self.device.download('/data/local/tmp/screenshot.png', local_path)
+
+ if success:
+ self.print_panel("SUCCESS", f"Screenshot saved to {local_path}")
+ else:
+ self.print_panel("ERROR", "Failed to download screenshot!", color="red")
+
+ self.device.send_command("rm /data/local/tmp/screenshot.png")
diff --git a/ghost/modules/shell.py b/ghost/modules/shell.py
new file mode 100644
index 00000000..bad6bb45
--- /dev/null
+++ b/ghost/modules/shell.py
@@ -0,0 +1,42 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "shell",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Execute shell command on device.",
+ 'Usage': "shell ",
+ 'MinArgs': 1,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message if message else "No output.")),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ command = ' '.join(args[1:])
+ self.print_panel("PROCESS", f"Executing shell command: {command}")
+ output = self.device.send_command(command)
+ self.print_panel("OUTPUT", output)
diff --git a/ghost/modules/sleep.py b/ghost/modules/sleep.py
new file mode 100644
index 00000000..9b8af222
--- /dev/null
+++ b/ghost/modules/sleep.py
@@ -0,0 +1,41 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "sleep",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Put device into sleep mode.",
+ 'Usage': "sleep",
+ 'MinArgs': 0,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, _):
+ self.print_panel("PROCESS", "Putting device into sleep mode...")
+ self.device.send_command("input keyevent 26")
+ self.print_panel("SUCCESS", "Device is now in sleep mode!")
diff --git a/ghost/modules/upload.py b/ghost/modules/upload.py
new file mode 100644
index 00000000..f28b9c0a
--- /dev/null
+++ b/ghost/modules/upload.py
@@ -0,0 +1,47 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+import os
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "manage",
+ 'Name': "upload",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Upload file to device.",
+ 'Usage': "upload ",
+ 'MinArgs': 2,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ local_file, remote_path = args[1], args[2]
+ self.print_panel("PROCESS", f"Uploading {local_file} to {remote_path}...")
+
+ success = self.device.upload(local_file, remote_path)
+ if success:
+ self.print_panel("SUCCESS", f"File uploaded to {remote_path}")
+ else:
+ self.print_panel("ERROR", "Upload failed!", color="red")
diff --git a/ghost/modules/wifi.py b/ghost/modules/wifi.py
new file mode 100644
index 00000000..c6dcbf3e
--- /dev/null
+++ b/ghost/modules/wifi.py
@@ -0,0 +1,46 @@
+"""
+This module requires Ghost: https://github.com/EntySec/Ghost
+Current source: https://github.com/EntySec/Ghost
+"""
+
+from badges.cmd import Command
+from rich.console import Console
+from rich.panel import Panel
+from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
+
+
+class ExternalCommand(Command):
+ def __init__(self):
+ super().__init__({
+ 'Category': "settings",
+ 'Name': "wifi",
+ 'Authors': [
+ 'Ivan Nikolskiy (enty8080) - module developer'
+ ],
+ 'Description': "Set device wifi service state.",
+ 'Usage': "wifi ",
+ 'MinArgs': 1,
+ 'NeedsRoot': False
+ })
+
+ def print_panel(self, title: str, message: str, color: str = _PURPLE):
+ panel = Panel.fit(
+ Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color
+ )
+ _console.print(panel)
+
+ def run(self, args):
+ state = args[1].lower()
+ if state in ['on', 'off']:
+ action = "enable" if state == 'on' else "disable"
+ self.print_panel("PROCESS", f"Turning WiFi {state}...")
+ self.device.send_command(f"svc wifi {action}")
+ self.print_panel("SUCCESS", f"WiFi turned {state} successfully!")
+ else:
+ self.print_panel("USAGE", f"Usage: {self.info['Usage']}", color="red")
From 1b20270110c94d468f7c52e9f9513feaed46deeb Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 17:52:57 -0400
Subject: [PATCH 25/47] Big update
---
README.md | 19 +++++++++++++++++++
main.py => ghost/main.py | 0
2 files changed, 19 insertions(+)
rename main.py => ghost/main.py (100%)
diff --git a/README.md b/README.md
index 1128116d..ba647200 100755
--- a/README.md
+++ b/README.md
@@ -31,3 +31,22 @@
pip3 install git+https://github.com/EntySec/Ghost
+
+# Ghost
+
+Ghost — staged installation framework
+
+## install
+
+```bash
+
+python -m venv .venv
+source .venv/bin/activate
+
+
+pip install --upgrade pip
+pip install .[vcs]
+
+
+ghost
+
diff --git a/main.py b/ghost/main.py
similarity index 100%
rename from main.py
rename to ghost/main.py
From f50301b61e6a8d8cd31939603a8765b1efc56ef2 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 17:56:05 -0400
Subject: [PATCH 26/47] Update pyproject.toml
---
pyproject.toml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index caa2a3fd..27afff5f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,13 +7,14 @@ name = "ghost"
version = "1.0.0"
description = "Ghost — staged installation framework"
authors = [
- { name="Your Name", email="you@example.com" }
+ { name="enty8080 ", email="enty8080@gmail.com" }
]
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
- "adb-shell"
+ "adb-shell",
+ "rich"
]
optional-dependencies = {
From 0eb33d0af992bc1d0250e19b7ad2ae80dba4d5ad Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:01:18 -0400
Subject: [PATCH 27/47] Update pyproject.toml
---
pyproject.toml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 27afff5f..35fcc2e1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -19,9 +19,9 @@ dependencies = [
optional-dependencies = {
vcs = [
- "badges @ git+https://github.com/EntySec/Badges",
- "pex @ git+https://github.com/EntySec/Pex",
- "colorscript @ git+https://github.com/EntySec/ColorScript"
+ "badges@git+https://github.com/EntySec/Badges",
+ "pex@git+https://github.com/EntySec/Pex",
+ "colorscript@git+https://github.com/EntySec/ColorScript"
]
}
From 3385696a3594fffdeac1d9d257c84dabe030080d Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:03:57 -0400
Subject: [PATCH 28/47] last Update pyproject.toml
---
pyproject.toml | 20 +++++++-------------
1 file changed, 7 insertions(+), 13 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 35fcc2e1..fce4c232 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,24 +6,18 @@ build-backend = "setuptools.build_meta"
name = "ghost"
version = "1.0.0"
description = "Ghost — staged installation framework"
-authors = [
- { name="enty8080 ", email="enty8080@gmail.com" }
-]
+authors = [{ name="Your Name", email="you@example.com" }]
readme = "README.md"
requires-python = ">=3.9"
+dependencies = ["adb-shell", "rich"]
-dependencies = [
- "adb-shell",
- "rich"
+[project.optional-dependencies]
+vcs = [
+ "badges@git+https://github.com/EntySec/Badges",
+ "pex@git+https://github.com/EntySec/Pex",
+ "colorscript@git+https://github.com/EntySec/ColorScript"
]
-optional-dependencies = {
- vcs = [
- "badges@git+https://github.com/EntySec/Badges",
- "pex@git+https://github.com/EntySec/Pex",
- "colorscript@git+https://github.com/EntySec/ColorScript"
- ]
-}
[project.scripts]
ghost = "ghost.main:main"
From a393e0276642185b247cbdf9c1bd28db5a161ed1 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:06:15 -0400
Subject: [PATCH 29/47] Remoce some comments
---
ghost/main.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/ghost/main.py b/ghost/main.py
index 0cc4f0e0..5fcb669e 100755
--- a/ghost/main.py
+++ b/ghost/main.py
@@ -70,7 +70,6 @@ def main() -> None:
use_brek = args.brek
use_venv = not args.no_venv
- # انتخاب خودکار در حالت بدون prompt
if not args.yes:
if not (args.brek or args.no_venv):
choice = Prompt.ask(
From 6b23ddefa2c2a1de7b084bca1857af7c4ad99328 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:07:03 -0400
Subject: [PATCH 30/47] Remoce some comments
---
ghost/core/console.py | 22 ++--------------------
1 file changed, 2 insertions(+), 20 deletions(-)
diff --git a/ghost/core/console.py b/ghost/core/console.py
index b8ba6fc4..7cd21cdd 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -26,7 +26,6 @@
from ghost.core.device import Device
-# --- Rich UI imports for styling (visual-only; logic untouched) ---
from rich.console import Console as RichConsole
from rich.panel import Panel
from rich.table import Table
@@ -39,7 +38,6 @@
from rich.columns import Columns
from rich.markdown import Markdown
-# Theme colors
PURPLE = "#7B61FF"
WHITE_ON_PURPLE = Style(color="white", bgcolor=PURPLE, bold=True)
INFO_STYLE = Style(color=PURPLE, bold=True)
@@ -56,7 +54,6 @@ class Console(Cmd):
"""
def __init__(self) -> None:
- # keep original prompt/intro tokens so framework behavior is unchanged
super().__init__(
prompt='(%lineghost%end)> ',
intro="""%clear%end
@@ -71,16 +68,12 @@ def __init__(self) -> None:
"""
)
- # preserve devices dict & logic
self.devices = {}
- # create rich console for all visual outputs
self.rich = RichConsole()
self._render_header()
- # -------------------------
- # Visual helpers (only)
- # -------------------------
+
def _render_header(self) -> None:
"""Render a fancy hacker-style header with tools table and quick help."""
title = Text("Ghost Framework 8.0.0", style="bold white")
@@ -104,7 +97,6 @@ def _render_header(self) -> None:
subtitle=title,
)
- # Quick Help / Shortcuts table
quick_help = Table(box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
quick_help.add_column("Command / Alias", style="bold white", no_wrap=True)
quick_help.add_column("Description", style="dim")
@@ -138,11 +130,8 @@ def _render_header(self) -> None:
def print_empty(self, message: str = "") -> None:
- # preserve signature and behavior, but print a small spacer
- self.rich.print("") # purely visual
-
+ self.rich.print("")
def print_information(self, message: str) -> None:
- # visually enhanced info box (preserves semantics)
self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
def print_warning(self, message: str) -> None:
@@ -155,15 +144,12 @@ def print_success(self, message: str) -> None:
self.rich.print(Panel(Text(message), border_style="green", title="[bold white]SUCCESS", box=box.MINIMAL))
def print_usage(self, usage: str) -> None:
- # show usage in an emphasized box
usage_text = Text.assemble(("Usage: ", "bold"), (usage, ""))
footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
self.rich.print(Panel(usage_text, border_style=PURPLE, title="[bold]USAGE", subtitle=footer))
def print_process(self, message: str) -> None:
- # lightweight status indicator (visual only)
with self.rich.status(Text(message, style=INFO_STYLE), spinner="bouncingBall", spinner_style=PURPLE):
- # no blocking logic here — just visual affordance
pass
def print_table(self, title: str, columns: tuple, *rows) -> None:
@@ -172,15 +158,11 @@ def print_table(self, title: str, columns: tuple, *rows) -> None:
for col in columns:
table.add_column(str(col), header_style="bold white")
for row in rows:
- # ensure each item is string (matching original behavior)
table.add_row(*[str(x) for x in row])
footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
wrapper = Panel(Padding(table, (0, 1)), subtitle=footer, border_style=PURPLE)
self.rich.print(wrapper)
- # -------------------------
- # Original command logic (UNCHANGED)
- # -------------------------
def do_exit(self, _) -> None:
""" Exit Ghost Framework.
From c2ad2224d2105bc42558c8842014086111e9c6e2 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:07:38 -0400
Subject: [PATCH 31/47] Remoce some comments
---
ghost/core/device.py | 21 +++------------------
1 file changed, 3 insertions(+), 18 deletions(-)
diff --git a/ghost/core/device.py b/ghost/core/device.py
index d8d364d4..b03f1617 100644
--- a/ghost/core/device.py
+++ b/ghost/core/device.py
@@ -63,9 +63,6 @@ def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename:
device=self
)
- # -------------------------
- # Rich Print Helpers
- # -------------------------
def print_panel(self, message: str, title: str, color: str = _PURPLE) -> None:
_console.print(Panel.fit(Align.left(Text(message)),
title=Text(title, style=f"bold white on {color}"),
@@ -77,9 +74,7 @@ def print_error(self, message: str) -> None: self.print_panel(message, "ERROR",
def print_information(self, message: str) -> None: self.print_panel(message, "INFO", _PURPLE)
def print_empty(self): _console.print()
- # -------------------------
- # Device Keys
- # -------------------------
+
def get_keys(self) -> tuple:
if not os.path.exists(self.key_file):
keygen(self.key_file)
@@ -89,9 +84,7 @@ def get_keys(self) -> tuple:
pub = f.read()
return pub, priv
- # -------------------------
- # Connection Management
- # -------------------------
+
def connect(self, auto_reconnect: bool = True) -> bool:
self._stop_reconnect.clear()
self.print_process(f"Connecting to {self.host}...")
@@ -142,9 +135,7 @@ def disconnect(self) -> None:
except Exception:
self.print_error("Failed to disconnect properly!")
- # -------------------------
- # Command / File Operations
- # -------------------------
+
def send_command(self, command: str, output: bool = True) -> str:
try:
cmd_output = self.device.shell(command)
@@ -198,9 +189,6 @@ def interact(self) -> None:
self.print_information(f"Modules loaded: {str(len(self.external))}")
self.loop()
- # -------------------------
- # Device Analyzer
- # -------------------------
def analyze_device(self):
self.print_process(f"Analyzing {self.host} ...")
try:
@@ -223,9 +211,6 @@ def analyze_device(self):
except Exception as e:
self.print_error(f"Analysis failed: {e}")
- # -------------------------
- # Real-Time Log Viewer
- # -------------------------
def live_logcat(self):
self.print_information("Starting live logcat stream (Press Ctrl+C to stop)...")
From 38e083b314c75a324e210b0828f38a4d3e832daf Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:13:21 -0400
Subject: [PATCH 32/47] Remove main.py | update pyproject.toml
---
ghost/main.py | 117 -------------------------------------------------
pyproject.toml | 9 ++--
2 files changed, 5 insertions(+), 121 deletions(-)
delete mode 100755 ghost/main.py
diff --git a/ghost/main.py b/ghost/main.py
deleted file mode 100755
index 5fcb669e..00000000
--- a/ghost/main.py
+++ /dev/null
@@ -1,117 +0,0 @@
-from __future__ import annotations
-import os
-import subprocess
-import sys
-from pathlib import Path
-from typing import List, Tuple
-
-from rich.console import Console
-from rich.panel import Panel
-from rich.prompt import Prompt
-from rich.text import Text
-import argparse
-
-console = Console()
-ROOT = Path(__file__).parent.resolve()
-
-VCS_PACKAGES = [
- "badges @ git+https://github.com/EntySec/Badges",
- "pex @ git+https://github.com/EntySec/Pex",
- "colorscript @ git+https://github.com/EntySec/ColorScript",
- "adb-shell"
-]
-
-DEFAULT_VENV = ROOT / ".venv"
-
-def run(cmd: List[str], env: dict | None = None, check: bool = True) -> None:
- console.log(f"[bold purple]$[/bold purple] {' '.join(cmd)}")
- res = subprocess.run(cmd, env=env)
- if check and res.returncode != 0:
- raise SystemExit(f"Command failed: {' '.join(cmd)} (exit {res.returncode})")
-
-def ensure_virtualenv(venv_path: Path) -> Tuple[str, str]:
- if venv_path.exists():
- console.print(Panel(f"Using existing virtualenv: [bold]{venv_path}[/bold]", title="Virtualenv"))
- else:
- console.print(Panel(f"Creating virtualenv at: [bold]{venv_path}[/bold]", title="Virtualenv"))
- run([sys.executable, "-m", "venv", str(venv_path)])
- if os.name == "nt":
- py = venv_path / "Scripts" / "python.exe"
- pip = venv_path / "Scripts" / "pip.exe"
- else:
- py = venv_path / "bin" / "python"
- pip = venv_path / "bin" / "pip"
- return str(py), str(pip)
-
-def install_packages(py_exe: str, packages: List[str], break_system: bool = False) -> None:
- if not packages:
- return
- cmd = [py_exe, "-m", "pip", "install"] + packages
- if break_system:
- cmd.append("--break-system-packages")
- run(cmd)
-
-def install_local_package(py_exe: str, break_system: bool = False) -> None:
- cmd = [py_exe, "-m", "pip", "install", ".", "--no-deps"]
- if break_system:
- cmd.append("--break-system-packages")
- run(cmd)
-
-def main() -> None:
- parser = argparse.ArgumentParser(description="Ghost installer (venv or system)")
- parser.add_argument("--no-venv", action="store_true", help="Do not create/use virtualenv; install in current interpreter")
- parser.add_argument("--venv", default=".venv", help="Virtualenv path (default: .venv)")
- parser.add_argument("--brek", action="store_true", help="Install only python packages (skip VCS URLs).")
- parser.add_argument("--yes", "-y", action="store_true", help="Automatic yes for prompts")
- args = parser.parse_args()
-
- console.print(Panel(Text("Ghost Installer — staged installation\n\nIndex 99 → Return to Menu / Exit", justify="center"), style="purple"))
-
- use_brek = args.brek
- use_venv = not args.no_venv
-
- if not args.yes:
- if not (args.brek or args.no_venv):
- choice = Prompt.ask(
- "Choose install mode",
- choices=["venv", "brek", "system"],
- default="venv",
- show_choices=True,
- )
- if choice == "venv":
- use_venv = True
- use_brek = False
- elif choice == "brek":
- use_venv = False
- use_brek = True
- else:
- use_venv = False
- use_brek = False
- else:
- if use_brek:
- use_venv = False
-
- break_system_flag = not use_venv
- if break_system_flag:
- console.print(Panel("Installing into current Python interpreter. --break-system-packages enabled", title="Notice", style="yellow"))
-
- if use_venv:
- py_exe, _ = ensure_virtualenv(Path(args.venv))
- else:
- py_exe = sys.executable
-
- console.print("[bold]Upgrading pip in target environment...[/bold]")
- cmd_upgrade = [py_exe, "-m", "pip", "install", "--upgrade", "pip"]
- if break_system_flag:
- cmd_upgrade.append("--break-system-packages")
- run(cmd_upgrade)
-
- if not use_brek:
- console.print(Panel(f"Installing VCS/GitHub packages ({len(VCS_PACKAGES)} items)...", title="Dependencies"))
- install_packages(py_exe, VCS_PACKAGES, break_system_flag)
-
- console.print(Panel("Installing Ghost main package...", style="purple"))
- install_local_package(py_exe, break_system_flag)
-
- console.print(Panel("[bold green]Installation completed successfully![/bold green]"))
- console.print("You can now run: ghost")
diff --git a/pyproject.toml b/pyproject.toml
index fce4c232..b9bd854b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,11 +5,13 @@ build-backend = "setuptools.build_meta"
[project]
name = "ghost"
version = "1.0.0"
-description = "Ghost — staged installation framework"
-authors = [{ name="Your Name", email="you@example.com" }]
+description = "Ghost CLI tool — no interactive installer"
readme = "README.md"
requires-python = ">=3.9"
-dependencies = ["adb-shell", "rich"]
+dependencies = [
+ "adb-shell",
+ "rich"
+]
[project.optional-dependencies]
vcs = [
@@ -18,6 +20,5 @@ vcs = [
"colorscript@git+https://github.com/EntySec/ColorScript"
]
-
[project.scripts]
ghost = "ghost.main:main"
From 97728268d1766359d4aae789646d5576753ac8e2 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:17:08 -0400
Subject: [PATCH 33/47] Add main.py
---
ghost/main.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
create mode 100644 ghost/main.py
diff --git a/ghost/main.py b/ghost/main.py
new file mode 100644
index 00000000..099fad6a
--- /dev/null
+++ b/ghost/main.py
@@ -0,0 +1,13 @@
+from rich.console import Console
+console = Console()
+
+def main():
+ console.print("[bold purple]Ghost CLI — Ready![/bold purple]")
+ while True:
+ cmd = input("ghost> ")
+ if cmd in ("exit", "quit"):
+ break
+ console.print(f"You typed: {cmd}")
+
+if __name__ == "__main__":
+ main()
From 5782752a2382b5e899a5351f5df68dfa418e3973 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:33:28 -0400
Subject: [PATCH 34/47] Big update 2
---
ghost/__init__.py | 10 +++-------
ghost/main.py | 13 -------------
pyproject.toml | 15 ++++++---------
3 files changed, 9 insertions(+), 29 deletions(-)
delete mode 100644 ghost/main.py
diff --git a/ghost/__init__.py b/ghost/__init__.py
index 5cc61709..959bf79a 100644
--- a/ghost/__init__.py
+++ b/ghost/__init__.py
@@ -24,12 +24,8 @@
from ghost.core.console import Console
-
-def cli() -> None:
- """ Ghost Framework command-line interface.
-
- :return None: None
- """
-
+def main():
+ """Ghost CLI entry-point."""
console = Console()
console.shell()
+
diff --git a/ghost/main.py b/ghost/main.py
deleted file mode 100644
index 099fad6a..00000000
--- a/ghost/main.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from rich.console import Console
-console = Console()
-
-def main():
- console.print("[bold purple]Ghost CLI — Ready![/bold purple]")
- while True:
- cmd = input("ghost> ")
- if cmd in ("exit", "quit"):
- break
- console.print(f"You typed: {cmd}")
-
-if __name__ == "__main__":
- main()
diff --git a/pyproject.toml b/pyproject.toml
index b9bd854b..1804dfad 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,20 +5,17 @@ build-backend = "setuptools.build_meta"
[project]
name = "ghost"
version = "1.0.0"
-description = "Ghost CLI tool — no interactive installer"
+description = "Ghost CLI tool"
readme = "README.md"
requires-python = ">=3.9"
-dependencies = [
- "adb-shell",
- "rich"
-]
+dependencies = ["rich", "adb-shell"]
[project.optional-dependencies]
vcs = [
- "badges@git+https://github.com/EntySec/Badges",
- "pex@git+https://github.com/EntySec/Pex",
- "colorscript@git+https://github.com/EntySec/ColorScript"
+ "badges @ git+https://github.com/EntySec/Badges",
+ "pex @ git+https://github.com/EntySec/Pex",
+ "colorscript @ git+https://github.com/EntySec/ColorScript"
]
[project.scripts]
-ghost = "ghost.main:main"
+ghost = "ghost:main"
From d578246c00e22295fa8902da16bad1c57b065808 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:36:02 -0400
Subject: [PATCH 35/47] update pyproject.toml
---
pyproject.toml | 8 +++++++-
requirements.txt | 3 ++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 1804dfad..a6f3a983 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,7 +8,12 @@ version = "1.0.0"
description = "Ghost CLI tool"
readme = "README.md"
requires-python = ">=3.9"
-dependencies = ["rich", "adb-shell"]
+
+dependencies = [
+ "adb-shell",
+ "rich<13.0.0"
+]
+
[project.optional-dependencies]
vcs = [
@@ -17,5 +22,6 @@ vcs = [
"colorscript @ git+https://github.com/EntySec/ColorScript"
]
+
[project.scripts]
ghost = "ghost:main"
diff --git a/requirements.txt b/requirements.txt
index 2aa89705..cfb4ba15 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,4 +2,5 @@ adb-shell>=0.1
pex @ git+https://github.com/EntySec/Pex.git
badges @ git+https://github.com/EntySec/Badges.git
colorscript @ git+https://github.com/EntySec/ColorScript.git
-rich>=10.0
+rich>=13.0.0
+
From 9e93c4bc5686ec086d33c30dfee806f3a193f201 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 18:38:52 -0400
Subject: [PATCH 36/47] update REAADME.md
---
README.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/README.md b/README.md
index ba647200..3bb9f062 100755
--- a/README.md
+++ b/README.md
@@ -36,6 +36,14 @@
Ghost — staged installation framework
+## fast-install
+
+```bash
+
+pip install .[vcs] --break
+
+```
+
## install
```bash
@@ -50,3 +58,5 @@ pip install .[vcs]
ghost
+```
+
From 180e94cf14507d01e420974be3e0fd64375b2611 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 19:01:50 -0400
Subject: [PATCH 37/47] last update
---
ghost/core/console.py | 41 +++++++++++++++++++++++++----------------
1 file changed, 25 insertions(+), 16 deletions(-)
diff --git a/ghost/core/console.py b/ghost/core/console.py
index 7cd21cdd..e72c7901 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -97,20 +97,30 @@ def _render_header(self) -> None:
subtitle=title,
)
- quick_help = Table(box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
- quick_help.add_column("Command / Alias", style="bold white", no_wrap=True)
- quick_help.add_column("Description", style="dim")
-
- quick_help.add_row("connect :[port]", "Connect to device via ADB (default port 5555)")
- quick_help.add_row("devices", "List connected devices")
- quick_help.add_row("disconnect ", "Disconnect device by ID")
- quick_help.add_row("interact ", "Interact with a connected device")
- quick_help.add_row("analyze ", "Run Device Analyzer")
- quick_help.add_row("an ", "Alias for analyze")
- quick_help.add_row("logcat ", "Start live logcat stream")
- quick_help.add_row("lc ", "Alias for logcat")
- quick_help.add_row("exit", "Quit Ghost Framework")
- quick_help.add_row("Index 99", "Return to Menu / Exit (UI helper)")
+ help_table = Table(title=Text("🚀 Ghost Framework Commands", style="bold white on " + PURPLE, justify="center"),
+ box=box.DOUBLE_EDGE,
+ border_style=PURPLE,
+ expand=False,
+ show_lines=True)
+
+ help_table.add_column("Command", style="bold white on " + "#5A3EFF", no_wrap=True, justify="center")
+ help_table.add_column("Description", style="italic dim", justify="left")
+
+ commands = [
+ ("🔌 connect :[port]", "Connect to device via ADB (default port 5555)"),
+ ("📱 devices", "List connected devices"),
+ ("❌ disconnect ", "Disconnect device by ID"),
+ ("💬 interact ", "Interact with a connected device"),
+ ("🔍 analyze / an ", "Run Device Analyzer"),
+ ("📜 logcat / lc ", "Start live logcat stream"),
+ ("🚪 exit", "Quit Ghost Framework"),
+ ("🔄 Index 99", "Return to Menu / Exit (UI helper)")
+ ]
+
+ ALT_ROW = "#2E2E2E"
+ for i, (cmd, desc) in enumerate(commands):
+ style = Style(bgcolor=ALT_ROW) if i % 2 else Style()
+ help_table.add_row(cmd, desc, style=style)
right_panel = Panel(
Align.left(
@@ -122,13 +132,12 @@ def _render_header(self) -> None:
title="[bold]Info",
)
- header_columns = Columns([left, Panel(Padding(quick_help, (1, 2)), border_style=PURPLE), right_panel])
+ header_columns = Columns([left, Panel(help_table, padding=(1, 2), border_style=PURPLE), right_panel])
self.rich.print(header_columns)
self.rich.print(Rule(style=PURPLE))
self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
self.rich.print()
-
def print_empty(self, message: str = "") -> None:
self.rich.print("")
def print_information(self, message: str) -> None:
From cf75102c94881bfe6c419518e57076d785cbf43b Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 19:45:03 -0400
Subject: [PATCH 38/47] Update pyproject.toml
---
pyproject.toml | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index a6f3a983..d8389c3a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,17 +11,11 @@ requires-python = ">=3.9"
dependencies = [
"adb-shell",
- "rich<13.0.0"
-]
-
-
-[project.optional-dependencies]
-vcs = [
+ "rich<13.0.0",
"badges @ git+https://github.com/EntySec/Badges",
"pex @ git+https://github.com/EntySec/Pex",
"colorscript @ git+https://github.com/EntySec/ColorScript"
]
-
[project.scripts]
ghost = "ghost:main"
From 81b1d48493caf7b61a335c1fc7d6eab9ec86e751 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 19:51:29 -0400
Subject: [PATCH 39/47] Update console.py & device.py
---
ghost/core/console.py | 170 ++++++++----------------------------------
ghost/core/device.py | 96 +++++++++---------------
2 files changed, 67 insertions(+), 199 deletions(-)
diff --git a/ghost/core/console.py b/ghost/core/console.py
index e72c7901..c87157b5 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -23,9 +23,7 @@
"""
from badges.cmd import Cmd
-
from ghost.core.device import Device
-
from rich.console import Console as RichConsole
from rich.panel import Panel
from rich.table import Table
@@ -36,22 +34,12 @@
from rich.rule import Rule
from rich.padding import Padding
from rich.columns import Columns
-from rich.markdown import Markdown
PURPLE = "#7B61FF"
-WHITE_ON_PURPLE = Style(color="white", bgcolor=PURPLE, bold=True)
INFO_STYLE = Style(color=PURPLE, bold=True)
-WARN_STYLE = Style(color="yellow", bold=True)
-ERR_STYLE = Style(color="red", bold=True)
-SUCCESS_STYLE = Style(color="green", bold=True)
-
class Console(Cmd):
- """ Subclass of ghost.core module.
-
- This subclass of ghost.core modules is intended for providing
- main Ghost Framework console interface.
- """
+ """ Subclass of ghost.core module using badges for printing. """
def __init__(self) -> None:
super().__init__(
@@ -69,13 +57,10 @@ def __init__(self) -> None:
)
self.devices = {}
-
- self.rich = RichConsole()
self._render_header()
-
def _render_header(self) -> None:
- """Render a fancy hacker-style header with tools table and quick help."""
+ """Render fancy header with modern Help table using Rich."""
title = Text("Ghost Framework 8.0.0", style="bold white")
subtitle = Text("Developed by EntySec — https://entysec.com/", style="dim")
@@ -97,13 +82,16 @@ def _render_header(self) -> None:
subtitle=title,
)
- help_table = Table(title=Text("🚀 Ghost Framework Commands", style="bold white on " + PURPLE, justify="center"),
- box=box.DOUBLE_EDGE,
- border_style=PURPLE,
- expand=False,
- show_lines=True)
+ # Help table
+ help_table = Table(
+ title=Text("🚀 Ghost Framework Commands", style="bold white on " + PURPLE, justify="center"),
+ box=box.DOUBLE_EDGE,
+ border_style=PURPLE,
+ expand=False,
+ show_lines=True
+ )
- help_table.add_column("Command", style="bold white on " + "#5A3EFF", no_wrap=True, justify="center")
+ help_table.add_column("Command", style="bold white on #5A3EFF", no_wrap=True, justify="center")
help_table.add_column("Description", style="italic dim", justify="left")
commands = [
@@ -133,158 +121,62 @@ def _render_header(self) -> None:
)
header_columns = Columns([left, Panel(help_table, padding=(1, 2), border_style=PURPLE), right_panel])
- self.rich.print(header_columns)
- self.rich.print(Rule(style=PURPLE))
- self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
- self.rich.print()
-
- def print_empty(self, message: str = "") -> None:
- self.rich.print("")
- def print_information(self, message: str) -> None:
- self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
-
- def print_warning(self, message: str) -> None:
- self.rich.print(Panel(Text(message), border_style="yellow", title="[bold white]WARNING", box=box.MINIMAL))
-
- def print_error(self, message: str) -> None:
- self.rich.print(Panel(Text(message), border_style="red", title="[bold white]ERROR", box=box.MINIMAL))
-
- def print_success(self, message: str) -> None:
- self.rich.print(Panel(Text(message), border_style="green", title="[bold white]SUCCESS", box=box.MINIMAL))
-
- def print_usage(self, usage: str) -> None:
- usage_text = Text.assemble(("Usage: ", "bold"), (usage, ""))
- footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
- self.rich.print(Panel(usage_text, border_style=PURPLE, title="[bold]USAGE", subtitle=footer))
-
- def print_process(self, message: str) -> None:
- with self.rich.status(Text(message, style=INFO_STYLE), spinner="bouncingBall", spinner_style=PURPLE):
- pass
-
- def print_table(self, title: str, columns: tuple, *rows) -> None:
- """Render a stylized table for lists like connected devices."""
- table = Table(title=title, box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
- for col in columns:
- table.add_column(str(col), header_style="bold white")
- for row in rows:
- table.add_row(*[str(x) for x in row])
- footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
- wrapper = Panel(Padding(table, (0, 1)), subtitle=footer, border_style=PURPLE)
- self.rich.print(wrapper)
+ self.print(header_columns)
+ self.print(Rule(style=PURPLE))
+ self.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
+ self.print()
+ # ===== Device commands =====
def do_exit(self, _) -> None:
- """ Exit Ghost Framework.
-
- :return None: None
- :raises EOFError: EOF error
- """
-
for device in list(self.devices):
self.devices[device]['device'].disconnect()
del self.devices[device]
-
raise EOFError
def do_connect(self, args: list) -> None:
- """ Connect device.
-
- :param list args: arguments
- :return None: None
- """
-
if len(args) < 2:
- self.print_usage("connect :[port]")
+ self.usage("connect :[port]")
return
address = args[1].split(':')
-
- if len(address) < 2:
- host, port = address[0], 5555
- else:
- host, port = address[0], int(address[1])
-
+ host, port = (address[0], 5555) if len(address) < 2 else (address[0], int(address[1]))
device = Device(host=host, port=port)
if device.connect():
- self.devices.update({
- len(self.devices): {
- 'host': host,
- 'port': str(port),
- 'device': device
- }
- })
- self.print_empty("")
-
- self.print_information(
- f"Type %greendevices%end to list all connected devices.")
- self.print_information(
- f"Type %greeninteract {str(len(self.devices) - 1)}%end "
- "to interact this device."
- )
+ self.devices.update({len(self.devices): {'host': host, 'port': str(port), 'device': device}})
+ self.info(f"Type %greendevices%end to list all connected devices.")
+ self.info(f"Type %greeninteract {len(self.devices) - 1}%end to interact with this device.")
def do_devices(self, _) -> None:
- """ Show connected devices.
-
- :return None: None
- """
-
if not self.devices:
- self.print_warning("No devices connected.")
+ self.warn("No devices connected.")
return
- devices = []
-
- for device in self.devices:
- devices.append(
- (device, self.devices[device]['host'],
- self.devices[device]['port']))
-
- self.print_table("Connected Devices", ('ID', 'Host', 'Port'), *devices)
+ devices = [(dev, self.devices[dev]['host'], self.devices[dev]['port']) for dev in self.devices]
+ self.table("Connected Devices", ("ID", "Host", "Port"), *devices)
def do_disconnect(self, args: list) -> None:
- """ Disconnect device.
-
- :param list args: arguments
- :return None: None
- """
-
if len(args) < 2:
- self.print_usage("disconnect ")
+ self.usage("disconnect ")
return
-
device_id = int(args[1])
-
if device_id not in self.devices:
- self.print_error("Invalid device ID!")
+ self.error("Invalid device ID!")
return
-
self.devices[device_id]['device'].disconnect()
self.devices.pop(device_id)
def do_interact(self, args: list) -> None:
- """ Interact with device.
-
- :param list args: arguments
- :return None: None
- """
-
if len(args) < 2:
- self.print_usage("interact ")
+ self.usage("interact ")
return
-
device_id = int(args[1])
-
if device_id not in self.devices:
- self.print_error("Invalid device ID!")
+ self.error("Invalid device ID!")
return
-
- self.print_process(f"Interacting with device {str(device_id)}...")
+ self.process(f"Interacting with device {device_id}...")
self.devices[device_id]['device'].interact()
-
- def shell(self) -> None:
- """ Run console shell.
-
- :return None: None
- """
+ def shell(self) -> None:
self.loop()
+
diff --git a/ghost/core/device.py b/ghost/core/device.py
index b03f1617..752ead7a 100644
--- a/ghost/core/device.py
+++ b/ghost/core/device.py
@@ -31,29 +31,22 @@
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from pex.fs import FS
-from rich.console import Console
-from rich.panel import Panel
from rich.table import Table
from rich.text import Text
-from rich.align import Align
-
-_PURPLE = "#7B61FF"
-_console = Console()
_MAX_RETRIES = 5
_RETRY_DELAY = 3
_HEALTHCHECK_DELAY = 10
-
+_PURPLE = "#7B61FF"
class Device(Cmd, FS):
- """Enhanced Device class with auto-reconnect, analyzer & log viewer."""
+ """Enhanced Device class fully compatible with badges."""
def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename: str = 'key') -> None:
self.host = host
self.port = int(port)
self.key_file = key_filename
self.device = AdbDeviceTcp(self.host, self.port, default_transport_timeout_s=timeout)
-
self._reconnect_thread = None
self._stop_reconnect = threading.Event()
@@ -63,18 +56,6 @@ def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename:
device=self
)
- def print_panel(self, message: str, title: str, color: str = _PURPLE) -> None:
- _console.print(Panel.fit(Align.left(Text(message)),
- title=Text(title, style=f"bold white on {color}"),
- border_style=color))
-
- def print_process(self, message: str) -> None: self.print_panel(message, "PROCESS", _PURPLE)
- def print_success(self, message: str) -> None: self.print_panel(message, "SUCCESS", "green")
- def print_error(self, message: str) -> None: self.print_panel(message, "ERROR", "red")
- def print_information(self, message: str) -> None: self.print_panel(message, "INFO", _PURPLE)
- def print_empty(self): _console.print()
-
-
def get_keys(self) -> tuple:
if not os.path.exists(self.key_file):
keygen(self.key_file)
@@ -84,28 +65,26 @@ def get_keys(self) -> tuple:
pub = f.read()
return pub, priv
-
def connect(self, auto_reconnect: bool = True) -> bool:
self._stop_reconnect.clear()
- self.print_process(f"Connecting to {self.host}...")
+ self.process(f"Connecting to {self.host}...")
keys = self.get_keys()
signer = PythonRSASigner(*keys)
for attempt in range(1, _MAX_RETRIES + 1):
try:
self.device.connect(rsa_keys=[signer], auth_timeout_s=5)
- self.print_success(f"Connected to {self.host}!")
-
+ self.success(f"Connected to {self.host}!")
if auto_reconnect:
self._start_auto_reconnect_thread()
return True
except Exception as e:
- self.print_error(f"[Attempt {attempt}/{_MAX_RETRIES}] Connection failed: {e}")
+ self.error(f"[Attempt {attempt}/{_MAX_RETRIES}] Connection failed: {e}")
if attempt < _MAX_RETRIES:
- self.print_information(f"Retrying in {_RETRY_DELAY} seconds...")
+ self.info(f"Retrying in {_RETRY_DELAY} seconds...")
time.sleep(_RETRY_DELAY)
- self.print_error(f"Failed to connect to {self.host} after {_MAX_RETRIES} attempts!")
+ self.error(f"Failed to connect to {self.host} after {_MAX_RETRIES} attempts!")
return False
def _start_auto_reconnect_thread(self) -> None:
@@ -118,11 +97,11 @@ def auto_reconnect(self) -> None:
try:
self.device.shell("echo ping", transport_timeout_s=3)
except Exception:
- self.print_error(f"Lost connection to {self.host}, attempting reconnect...")
+ self.error(f"Lost connection to {self.host}, attempting reconnect...")
if not self.connect(auto_reconnect=False):
- self.print_error("Auto-reconnect failed, will retry again...")
+ self.error("Auto-reconnect failed, will retry again...")
else:
- self.print_success("Reconnected successfully!")
+ self.success("Reconnected successfully!")
time.sleep(_HEALTHCHECK_DELAY)
def disconnect(self) -> None:
@@ -131,16 +110,15 @@ def disconnect(self) -> None:
self._reconnect_thread.join(timeout=2)
try:
self.device.close()
- self.print_success(f"Disconnected from {self.host}.")
+ self.success(f"Disconnected from {self.host}.")
except Exception:
- self.print_error("Failed to disconnect properly!")
-
+ self.error("Failed to disconnect properly!")
def send_command(self, command: str, output: bool = True) -> str:
try:
cmd_output = self.device.shell(command)
except Exception:
- self.print_error("Socket is not connected!")
+ self.error("Socket is not connected!")
return None
return cmd_output if output else ""
@@ -148,7 +126,7 @@ def list(self, path: str) -> list:
try:
return self.device.list(path)
except Exception:
- self.print_error("Failed to list directory!")
+ self.error("Failed to list directory!")
return []
def download(self, input_file: str, output_path: str) -> bool:
@@ -156,27 +134,27 @@ def download(self, input_file: str, output_path: str) -> bool:
if exists:
if is_dir: output_path += '/' + os.path.split(input_file)[1]
try:
- self.print_process(f"Downloading {input_file}...")
+ self.process(f"Downloading {input_file}...")
self.device.pull(input_file, output_path)
- self.print_success(f"Saved to {output_path}!")
+ self.success(f"Saved to {output_path}!")
return True
except Exception:
- self.print_error(f"Remote file {input_file} not found or invalid!")
+ self.error(f"Remote file {input_file} not found or invalid!")
return False
def upload(self, input_file: str, output_path: str) -> bool:
if self.check_file(input_file):
try:
- self.print_process(f"Uploading {input_file}...")
+ self.process(f"Uploading {input_file}...")
self.device.push(input_file, output_path)
- self.print_success(f"Saved to {output_path}!")
+ self.success(f"Saved to {output_path}!")
return True
except Exception:
try:
output_path += '/' + os.path.split(input_file)[1]
self.device.push(input_file, output_path)
except Exception:
- self.print_error(f"Remote directory {output_path} does not exist!")
+ self.error(f"Remote directory {output_path} does not exist!")
return False
def is_rooted(self) -> bool:
@@ -184,13 +162,13 @@ def is_rooted(self) -> bool:
return bool(responder and not responder.isspace())
def interact(self) -> None:
- self.print_success("Interactive connection spawned!")
- self.print_process("Loading device modules...")
- self.print_information(f"Modules loaded: {str(len(self.external))}")
+ self.success("Interactive connection spawned!")
+ self.process("Loading device modules...")
+ self.info(f"Modules loaded: {str(len(self.external))}")
self.loop()
def analyze_device(self):
- self.print_process(f"Analyzing {self.host} ...")
+ self.process(f"Analyzing {self.host} ...")
try:
props = {
"Manufacturer": self.send_command("getprop ro.product.manufacturer"),
@@ -204,29 +182,27 @@ def analyze_device(self):
table = Table(title=f"📱 Device Analysis — {self.host}", border_style=_PURPLE)
table.add_column("Property", style="bold white")
table.add_column("Value", style="dim")
-
- for k, v in props.items(): table.add_row(k, v.strip() if v else "N/A")
- _console.print(table)
- self.print_success("Analysis complete!")
+ for k, v in props.items():
+ table.add_row(k, v.strip() if v else "N/A")
+ self.print(table)
+ self.success("Analysis complete!")
except Exception as e:
- self.print_error(f"Analysis failed: {e}")
+ self.error(f"Analysis failed: {e}")
def live_logcat(self):
- self.print_information("Starting live logcat stream (Press Ctrl+C to stop)...")
-
+ self.info("Starting live logcat stream (Press Ctrl+C to stop)...")
def stream_logs():
try:
shell = self.device.shell("logcat -v time", decode=False)
for line in shell:
decoded = line.decode(errors="ignore").strip()
- if " E " in decoded: _console.print(Text(decoded, style="red"))
- elif " W " in decoded: _console.print(Text(decoded, style="yellow"))
- else: _console.print(Text(decoded, style="dim"))
+ if " E " in decoded: self.print(Text(decoded, style="red"))
+ elif " W " in decoded: self.print(Text(decoded, style="yellow"))
+ else: self.print(Text(decoded, style="dim"))
except KeyboardInterrupt:
- self.print_process("Log streaming stopped by user.")
+ self.process("Log streaming stopped by user.")
except Exception as e:
- self.print_error(f"Logcat error: {e}")
+ self.error(f"Logcat error: {e}")
- t = threading.Thread(target=stream_logs)
- t.daemon = True
+ t = threading.Thread(target=stream_logs, daemon=True)
t.start()
From 6f06cbe592d6068bce035b6ac30960bb461fb0da Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 20:04:46 -0400
Subject: [PATCH 40/47] Update main to cli
---
ghost/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ghost/__init__.py b/ghost/__init__.py
index 959bf79a..3221e2a0 100644
--- a/ghost/__init__.py
+++ b/ghost/__init__.py
@@ -24,7 +24,7 @@
from ghost.core.console import Console
-def main():
+def cli():
"""Ghost CLI entry-point."""
console = Console()
console.shell()
From 10400291f267e53fb4225b7df3c31b1d8687859d Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 20:05:46 -0400
Subject: [PATCH 41/47] Update main to cli
---
ghost/core/console.py | 170 ++++++++++++++++++++++++++++++++++--------
ghost/core/device.py | 96 +++++++++++++++---------
pyproject.toml | 2 +-
3 files changed, 200 insertions(+), 68 deletions(-)
diff --git a/ghost/core/console.py b/ghost/core/console.py
index c87157b5..e72c7901 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -23,7 +23,9 @@
"""
from badges.cmd import Cmd
+
from ghost.core.device import Device
+
from rich.console import Console as RichConsole
from rich.panel import Panel
from rich.table import Table
@@ -34,12 +36,22 @@
from rich.rule import Rule
from rich.padding import Padding
from rich.columns import Columns
+from rich.markdown import Markdown
PURPLE = "#7B61FF"
+WHITE_ON_PURPLE = Style(color="white", bgcolor=PURPLE, bold=True)
INFO_STYLE = Style(color=PURPLE, bold=True)
+WARN_STYLE = Style(color="yellow", bold=True)
+ERR_STYLE = Style(color="red", bold=True)
+SUCCESS_STYLE = Style(color="green", bold=True)
+
class Console(Cmd):
- """ Subclass of ghost.core module using badges for printing. """
+ """ Subclass of ghost.core module.
+
+ This subclass of ghost.core modules is intended for providing
+ main Ghost Framework console interface.
+ """
def __init__(self) -> None:
super().__init__(
@@ -57,10 +69,13 @@ def __init__(self) -> None:
)
self.devices = {}
+
+ self.rich = RichConsole()
self._render_header()
+
def _render_header(self) -> None:
- """Render fancy header with modern Help table using Rich."""
+ """Render a fancy hacker-style header with tools table and quick help."""
title = Text("Ghost Framework 8.0.0", style="bold white")
subtitle = Text("Developed by EntySec — https://entysec.com/", style="dim")
@@ -82,16 +97,13 @@ def _render_header(self) -> None:
subtitle=title,
)
- # Help table
- help_table = Table(
- title=Text("🚀 Ghost Framework Commands", style="bold white on " + PURPLE, justify="center"),
- box=box.DOUBLE_EDGE,
- border_style=PURPLE,
- expand=False,
- show_lines=True
- )
+ help_table = Table(title=Text("🚀 Ghost Framework Commands", style="bold white on " + PURPLE, justify="center"),
+ box=box.DOUBLE_EDGE,
+ border_style=PURPLE,
+ expand=False,
+ show_lines=True)
- help_table.add_column("Command", style="bold white on #5A3EFF", no_wrap=True, justify="center")
+ help_table.add_column("Command", style="bold white on " + "#5A3EFF", no_wrap=True, justify="center")
help_table.add_column("Description", style="italic dim", justify="left")
commands = [
@@ -121,62 +133,158 @@ def _render_header(self) -> None:
)
header_columns = Columns([left, Panel(help_table, padding=(1, 2), border_style=PURPLE), right_panel])
- self.print(header_columns)
- self.print(Rule(style=PURPLE))
- self.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
- self.print()
+ self.rich.print(header_columns)
+ self.rich.print(Rule(style=PURPLE))
+ self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
+ self.rich.print()
+
+ def print_empty(self, message: str = "") -> None:
+ self.rich.print("")
+ def print_information(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
+
+ def print_warning(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="yellow", title="[bold white]WARNING", box=box.MINIMAL))
+
+ def print_error(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="red", title="[bold white]ERROR", box=box.MINIMAL))
+
+ def print_success(self, message: str) -> None:
+ self.rich.print(Panel(Text(message), border_style="green", title="[bold white]SUCCESS", box=box.MINIMAL))
+
+ def print_usage(self, usage: str) -> None:
+ usage_text = Text.assemble(("Usage: ", "bold"), (usage, ""))
+ footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
+ self.rich.print(Panel(usage_text, border_style=PURPLE, title="[bold]USAGE", subtitle=footer))
+
+ def print_process(self, message: str) -> None:
+ with self.rich.status(Text(message, style=INFO_STYLE), spinner="bouncingBall", spinner_style=PURPLE):
+ pass
+
+ def print_table(self, title: str, columns: tuple, *rows) -> None:
+ """Render a stylized table for lists like connected devices."""
+ table = Table(title=title, box=box.SIMPLE_HEAVY, expand=False, border_style=PURPLE)
+ for col in columns:
+ table.add_column(str(col), header_style="bold white")
+ for row in rows:
+ table.add_row(*[str(x) for x in row])
+ footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
+ wrapper = Panel(Padding(table, (0, 1)), subtitle=footer, border_style=PURPLE)
+ self.rich.print(wrapper)
- # ===== Device commands =====
def do_exit(self, _) -> None:
+ """ Exit Ghost Framework.
+
+ :return None: None
+ :raises EOFError: EOF error
+ """
+
for device in list(self.devices):
self.devices[device]['device'].disconnect()
del self.devices[device]
+
raise EOFError
def do_connect(self, args: list) -> None:
+ """ Connect device.
+
+ :param list args: arguments
+ :return None: None
+ """
+
if len(args) < 2:
- self.usage("connect :[port]")
+ self.print_usage("connect :[port]")
return
address = args[1].split(':')
- host, port = (address[0], 5555) if len(address) < 2 else (address[0], int(address[1]))
+
+ if len(address) < 2:
+ host, port = address[0], 5555
+ else:
+ host, port = address[0], int(address[1])
+
device = Device(host=host, port=port)
if device.connect():
- self.devices.update({len(self.devices): {'host': host, 'port': str(port), 'device': device}})
- self.info(f"Type %greendevices%end to list all connected devices.")
- self.info(f"Type %greeninteract {len(self.devices) - 1}%end to interact with this device.")
+ self.devices.update({
+ len(self.devices): {
+ 'host': host,
+ 'port': str(port),
+ 'device': device
+ }
+ })
+ self.print_empty("")
+
+ self.print_information(
+ f"Type %greendevices%end to list all connected devices.")
+ self.print_information(
+ f"Type %greeninteract {str(len(self.devices) - 1)}%end "
+ "to interact this device."
+ )
def do_devices(self, _) -> None:
+ """ Show connected devices.
+
+ :return None: None
+ """
+
if not self.devices:
- self.warn("No devices connected.")
+ self.print_warning("No devices connected.")
return
- devices = [(dev, self.devices[dev]['host'], self.devices[dev]['port']) for dev in self.devices]
- self.table("Connected Devices", ("ID", "Host", "Port"), *devices)
+ devices = []
+
+ for device in self.devices:
+ devices.append(
+ (device, self.devices[device]['host'],
+ self.devices[device]['port']))
+
+ self.print_table("Connected Devices", ('ID', 'Host', 'Port'), *devices)
def do_disconnect(self, args: list) -> None:
+ """ Disconnect device.
+
+ :param list args: arguments
+ :return None: None
+ """
+
if len(args) < 2:
- self.usage("disconnect ")
+ self.print_usage("disconnect ")
return
+
device_id = int(args[1])
+
if device_id not in self.devices:
- self.error("Invalid device ID!")
+ self.print_error("Invalid device ID!")
return
+
self.devices[device_id]['device'].disconnect()
self.devices.pop(device_id)
def do_interact(self, args: list) -> None:
+ """ Interact with device.
+
+ :param list args: arguments
+ :return None: None
+ """
+
if len(args) < 2:
- self.usage("interact ")
+ self.print_usage("interact ")
return
+
device_id = int(args[1])
+
if device_id not in self.devices:
- self.error("Invalid device ID!")
+ self.print_error("Invalid device ID!")
return
- self.process(f"Interacting with device {device_id}...")
- self.devices[device_id]['device'].interact()
+ self.print_process(f"Interacting with device {str(device_id)}...")
+ self.devices[device_id]['device'].interact()
+
def shell(self) -> None:
- self.loop()
+ """ Run console shell.
+ :return None: None
+ """
+
+ self.loop()
diff --git a/ghost/core/device.py b/ghost/core/device.py
index 752ead7a..b03f1617 100644
--- a/ghost/core/device.py
+++ b/ghost/core/device.py
@@ -31,22 +31,29 @@
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from pex.fs import FS
+from rich.console import Console
+from rich.panel import Panel
from rich.table import Table
from rich.text import Text
+from rich.align import Align
+
+_PURPLE = "#7B61FF"
+_console = Console()
_MAX_RETRIES = 5
_RETRY_DELAY = 3
_HEALTHCHECK_DELAY = 10
-_PURPLE = "#7B61FF"
+
class Device(Cmd, FS):
- """Enhanced Device class fully compatible with badges."""
+ """Enhanced Device class with auto-reconnect, analyzer & log viewer."""
def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename: str = 'key') -> None:
self.host = host
self.port = int(port)
self.key_file = key_filename
self.device = AdbDeviceTcp(self.host, self.port, default_transport_timeout_s=timeout)
+
self._reconnect_thread = None
self._stop_reconnect = threading.Event()
@@ -56,6 +63,18 @@ def __init__(self, host: str, port: int = 5555, timeout: int = 10, key_filename:
device=self
)
+ def print_panel(self, message: str, title: str, color: str = _PURPLE) -> None:
+ _console.print(Panel.fit(Align.left(Text(message)),
+ title=Text(title, style=f"bold white on {color}"),
+ border_style=color))
+
+ def print_process(self, message: str) -> None: self.print_panel(message, "PROCESS", _PURPLE)
+ def print_success(self, message: str) -> None: self.print_panel(message, "SUCCESS", "green")
+ def print_error(self, message: str) -> None: self.print_panel(message, "ERROR", "red")
+ def print_information(self, message: str) -> None: self.print_panel(message, "INFO", _PURPLE)
+ def print_empty(self): _console.print()
+
+
def get_keys(self) -> tuple:
if not os.path.exists(self.key_file):
keygen(self.key_file)
@@ -65,26 +84,28 @@ def get_keys(self) -> tuple:
pub = f.read()
return pub, priv
+
def connect(self, auto_reconnect: bool = True) -> bool:
self._stop_reconnect.clear()
- self.process(f"Connecting to {self.host}...")
+ self.print_process(f"Connecting to {self.host}...")
keys = self.get_keys()
signer = PythonRSASigner(*keys)
for attempt in range(1, _MAX_RETRIES + 1):
try:
self.device.connect(rsa_keys=[signer], auth_timeout_s=5)
- self.success(f"Connected to {self.host}!")
+ self.print_success(f"Connected to {self.host}!")
+
if auto_reconnect:
self._start_auto_reconnect_thread()
return True
except Exception as e:
- self.error(f"[Attempt {attempt}/{_MAX_RETRIES}] Connection failed: {e}")
+ self.print_error(f"[Attempt {attempt}/{_MAX_RETRIES}] Connection failed: {e}")
if attempt < _MAX_RETRIES:
- self.info(f"Retrying in {_RETRY_DELAY} seconds...")
+ self.print_information(f"Retrying in {_RETRY_DELAY} seconds...")
time.sleep(_RETRY_DELAY)
- self.error(f"Failed to connect to {self.host} after {_MAX_RETRIES} attempts!")
+ self.print_error(f"Failed to connect to {self.host} after {_MAX_RETRIES} attempts!")
return False
def _start_auto_reconnect_thread(self) -> None:
@@ -97,11 +118,11 @@ def auto_reconnect(self) -> None:
try:
self.device.shell("echo ping", transport_timeout_s=3)
except Exception:
- self.error(f"Lost connection to {self.host}, attempting reconnect...")
+ self.print_error(f"Lost connection to {self.host}, attempting reconnect...")
if not self.connect(auto_reconnect=False):
- self.error("Auto-reconnect failed, will retry again...")
+ self.print_error("Auto-reconnect failed, will retry again...")
else:
- self.success("Reconnected successfully!")
+ self.print_success("Reconnected successfully!")
time.sleep(_HEALTHCHECK_DELAY)
def disconnect(self) -> None:
@@ -110,15 +131,16 @@ def disconnect(self) -> None:
self._reconnect_thread.join(timeout=2)
try:
self.device.close()
- self.success(f"Disconnected from {self.host}.")
+ self.print_success(f"Disconnected from {self.host}.")
except Exception:
- self.error("Failed to disconnect properly!")
+ self.print_error("Failed to disconnect properly!")
+
def send_command(self, command: str, output: bool = True) -> str:
try:
cmd_output = self.device.shell(command)
except Exception:
- self.error("Socket is not connected!")
+ self.print_error("Socket is not connected!")
return None
return cmd_output if output else ""
@@ -126,7 +148,7 @@ def list(self, path: str) -> list:
try:
return self.device.list(path)
except Exception:
- self.error("Failed to list directory!")
+ self.print_error("Failed to list directory!")
return []
def download(self, input_file: str, output_path: str) -> bool:
@@ -134,27 +156,27 @@ def download(self, input_file: str, output_path: str) -> bool:
if exists:
if is_dir: output_path += '/' + os.path.split(input_file)[1]
try:
- self.process(f"Downloading {input_file}...")
+ self.print_process(f"Downloading {input_file}...")
self.device.pull(input_file, output_path)
- self.success(f"Saved to {output_path}!")
+ self.print_success(f"Saved to {output_path}!")
return True
except Exception:
- self.error(f"Remote file {input_file} not found or invalid!")
+ self.print_error(f"Remote file {input_file} not found or invalid!")
return False
def upload(self, input_file: str, output_path: str) -> bool:
if self.check_file(input_file):
try:
- self.process(f"Uploading {input_file}...")
+ self.print_process(f"Uploading {input_file}...")
self.device.push(input_file, output_path)
- self.success(f"Saved to {output_path}!")
+ self.print_success(f"Saved to {output_path}!")
return True
except Exception:
try:
output_path += '/' + os.path.split(input_file)[1]
self.device.push(input_file, output_path)
except Exception:
- self.error(f"Remote directory {output_path} does not exist!")
+ self.print_error(f"Remote directory {output_path} does not exist!")
return False
def is_rooted(self) -> bool:
@@ -162,13 +184,13 @@ def is_rooted(self) -> bool:
return bool(responder and not responder.isspace())
def interact(self) -> None:
- self.success("Interactive connection spawned!")
- self.process("Loading device modules...")
- self.info(f"Modules loaded: {str(len(self.external))}")
+ self.print_success("Interactive connection spawned!")
+ self.print_process("Loading device modules...")
+ self.print_information(f"Modules loaded: {str(len(self.external))}")
self.loop()
def analyze_device(self):
- self.process(f"Analyzing {self.host} ...")
+ self.print_process(f"Analyzing {self.host} ...")
try:
props = {
"Manufacturer": self.send_command("getprop ro.product.manufacturer"),
@@ -182,27 +204,29 @@ def analyze_device(self):
table = Table(title=f"📱 Device Analysis — {self.host}", border_style=_PURPLE)
table.add_column("Property", style="bold white")
table.add_column("Value", style="dim")
- for k, v in props.items():
- table.add_row(k, v.strip() if v else "N/A")
- self.print(table)
- self.success("Analysis complete!")
+
+ for k, v in props.items(): table.add_row(k, v.strip() if v else "N/A")
+ _console.print(table)
+ self.print_success("Analysis complete!")
except Exception as e:
- self.error(f"Analysis failed: {e}")
+ self.print_error(f"Analysis failed: {e}")
def live_logcat(self):
- self.info("Starting live logcat stream (Press Ctrl+C to stop)...")
+ self.print_information("Starting live logcat stream (Press Ctrl+C to stop)...")
+
def stream_logs():
try:
shell = self.device.shell("logcat -v time", decode=False)
for line in shell:
decoded = line.decode(errors="ignore").strip()
- if " E " in decoded: self.print(Text(decoded, style="red"))
- elif " W " in decoded: self.print(Text(decoded, style="yellow"))
- else: self.print(Text(decoded, style="dim"))
+ if " E " in decoded: _console.print(Text(decoded, style="red"))
+ elif " W " in decoded: _console.print(Text(decoded, style="yellow"))
+ else: _console.print(Text(decoded, style="dim"))
except KeyboardInterrupt:
- self.process("Log streaming stopped by user.")
+ self.print_process("Log streaming stopped by user.")
except Exception as e:
- self.error(f"Logcat error: {e}")
+ self.print_error(f"Logcat error: {e}")
- t = threading.Thread(target=stream_logs, daemon=True)
+ t = threading.Thread(target=stream_logs)
+ t.daemon = True
t.start()
diff --git a/pyproject.toml b/pyproject.toml
index d8389c3a..41d06993 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,4 +18,4 @@ dependencies = [
]
[project.scripts]
-ghost = "ghost:main"
+ghost = "ghost:cli"
From 32d2ebeed94ff412c2a023ea1d8211a54874588a Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 20:15:56 -0400
Subject: [PATCH 42/47] Remove requirements.txt & update pyproject.toml
---
pyproject.toml | 30 +++++++++++++++++++++---------
requirements.txt | 6 ------
2 files changed, 21 insertions(+), 15 deletions(-)
delete mode 100644 requirements.txt
diff --git a/pyproject.toml b/pyproject.toml
index 41d06993..922623c2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,21 +1,33 @@
[build-system]
-requires = ["setuptools>=65", "wheel"]
+requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "ghost"
-version = "1.0.0"
-description = "Ghost CLI tool"
+version = "8.0.0"
+description = "Ghost Framework is an Android post-exploitation framework that exploits the Android Debug Bridge to remotely access an Android device."
readme = "README.md"
-requires-python = ">=3.9"
+requires-python = ">=3.7.0"
+license = { file = "LICENSE" }
+authors = [
+ { name = "EntySec", email = "entysec@gmail.com" }
+]
+urls = { Homepage = "https://github.com/EntySec/Ghost" }
dependencies = [
- "adb-shell",
- "rich<13.0.0",
- "badges @ git+https://github.com/EntySec/Badges",
- "pex @ git+https://github.com/EntySec/Pex",
- "colorscript @ git+https://github.com/EntySec/ColorScript"
+ "adb-shell",
+ "pex @ git+https://github.com/EntySec/Pex",
+ "badges @ git+https://github.com/EntySec/Badges",
+ "colorscript @ git+https://github.com/EntySec/ColorScript",
]
[project.scripts]
ghost = "ghost:cli"
+
+[tool.setuptools.packages.find]
+where = ["."]
+include = ["*"]
+exclude = []
+
+[tool.setuptools]
+include-package-data = true
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index cfb4ba15..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-adb-shell>=0.1
-pex @ git+https://github.com/EntySec/Pex.git
-badges @ git+https://github.com/EntySec/Badges.git
-colorscript @ git+https://github.com/EntySec/ColorScript.git
-rich>=13.0.0
-
From d602f792a058e492afac810a7c44bfef590a0b77 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 20:21:20 -0400
Subject: [PATCH 43/47] update console.py
---
ghost/core/console.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ghost/core/console.py b/ghost/core/console.py
index e72c7901..1a8132e6 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -138,8 +138,8 @@ def _render_header(self) -> None:
self.rich.print(Align.center(Text("Type [bold]devices[/bold] to list connected devices — Index 99 → Exit", style=INFO_STYLE)))
self.rich.print()
- def print_empty(self, message: str = "") -> None:
- self.rich.print("")
+ def print_empty(self, message: str = "", end: str = "\n") -> None:
+ self.rich.print(message)
def print_information(self, message: str) -> None:
self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
From cb8b58102610d8d53e08221cad8d2e9a16e0ec9d Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 20:34:08 -0400
Subject: [PATCH 44/47] Add __main__.py
---
ghost/__init__.py | 30 ------------------------------
ghost/__main__.py | 37 +++++++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 30 deletions(-)
create mode 100644 ghost/__main__.py
diff --git a/ghost/__init__.py b/ghost/__init__.py
index 3221e2a0..d3f5a12f 100644
--- a/ghost/__init__.py
+++ b/ghost/__init__.py
@@ -1,31 +1 @@
-"""
-MIT License
-
-Copyright (c) 2020-2024 EntySec
-
-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.
-"""
-
-from ghost.core.console import Console
-
-def cli():
- """Ghost CLI entry-point."""
- console = Console()
- console.shell()
diff --git a/ghost/__main__.py b/ghost/__main__.py
new file mode 100644
index 00000000..6bbf5895
--- /dev/null
+++ b/ghost/__main__.py
@@ -0,0 +1,37 @@
+"""
+MIT License
+
+Copyright (c) 2020-2024 EntySec
+
+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.
+"""
+
+from ghost.core.console import Console
+
+def cli() -> None:
+ """ Ghost Framework command-line interface.
+
+ :return None: None
+ """
+
+ console = Console()
+ console.shell()
+
+if __name__ == "__main__":
+ cli()
\ No newline at end of file
From 0802ca5ffdf37a009c6f81d595c72156c909314a Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 21:06:06 -0400
Subject: [PATCH 45/47] last update
---
ghost/__init__.py | 24 ++++++++++++++++++++++++
ghost/__main__.py | 37 ++-----------------------------------
ghost/cli.py | 5 +++++
3 files changed, 31 insertions(+), 35 deletions(-)
create mode 100644 ghost/cli.py
diff --git a/ghost/__init__.py b/ghost/__init__.py
index d3f5a12f..fcc907ff 100644
--- a/ghost/__init__.py
+++ b/ghost/__init__.py
@@ -1 +1,25 @@
+"""
+MIT License
+Copyright (c) 2020-2024 EntySec
+
+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.
+"""
+
+__version__ = "…"
\ No newline at end of file
diff --git a/ghost/__main__.py b/ghost/__main__.py
index 6bbf5895..0ef3024d 100644
--- a/ghost/__main__.py
+++ b/ghost/__main__.py
@@ -1,37 +1,4 @@
-"""
-MIT License
-
-Copyright (c) 2020-2024 EntySec
-
-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.
-"""
-
-from ghost.core.console import Console
-
-def cli() -> None:
- """ Ghost Framework command-line interface.
-
- :return None: None
- """
-
- console = Console()
- console.shell()
+from .cli import main
if __name__ == "__main__":
- cli()
\ No newline at end of file
+ raise SystemExit(main())
\ No newline at end of file
diff --git a/ghost/cli.py b/ghost/cli.py
new file mode 100644
index 00000000..d5bf9ef7
--- /dev/null
+++ b/ghost/cli.py
@@ -0,0 +1,5 @@
+from ghost.core.console import Console
+
+def main() -> int:
+ Console().shell()
+ return 0
\ No newline at end of file
From 566c371b3af8df4c51c3306d96ac5e310b738f01 Mon Sep 17 00:00:00 2001
From: Modark
Date: Fri, 17 Oct 2025 21:06:23 -0400
Subject: [PATCH 46/47] last update
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 922623c2..3b673e40 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,7 +22,7 @@ dependencies = [
]
[project.scripts]
-ghost = "ghost:cli"
+ghost = "ghost.cli:main"
[tool.setuptools.packages.find]
where = ["."]
From fa39dbf581a298f070cd92e729fd58c20303359f Mon Sep 17 00:00:00 2001
From: Modark
Date: Sat, 18 Oct 2025 06:01:30 -0400
Subject: [PATCH 47/47] update interfaace
---
ghost/core/console.py | 70 +++++++++++++++----------------------------
1 file changed, 24 insertions(+), 46 deletions(-)
diff --git a/ghost/core/console.py b/ghost/core/console.py
index 1a8132e6..da7de45e 100644
--- a/ghost/core/console.py
+++ b/ghost/core/console.py
@@ -23,7 +23,6 @@
"""
from badges.cmd import Cmd
-
from ghost.core.device import Device
from rich.console import Console as RichConsole
@@ -36,7 +35,6 @@
from rich.rule import Rule
from rich.padding import Padding
from rich.columns import Columns
-from rich.markdown import Markdown
PURPLE = "#7B61FF"
WHITE_ON_PURPLE = Style(color="white", bgcolor=PURPLE, bold=True)
@@ -56,21 +54,23 @@ class Console(Cmd):
def __init__(self) -> None:
super().__init__(
prompt='(%lineghost%end)> ',
- intro="""%clear%end
+ intro="""
.--. .-. .-.
: .--': : .' `.
: : _ : `-. .--. .--.`. .'
: :; :: .. :' .; :`._-.': :
`.__.':_;:_;`.__.'`.__.':_;
---=[ %bold%whiteGhost Framework 8.0.0%end
---=[ Developed by EntySec (%linehttps://entysec.com/%end)
+--=[ Ghost Framework 8.0.0
+--=[ Developed by EntySec (https://entysec.com/)
"""
)
self.devices = {}
- self.rich = RichConsole()
+ # Force ANSI + TrueColor for Linux/modern terminals
+ self.rich = RichConsole(force_terminal=True, color_system="truecolor")
+ self.rich.clear()
self._render_header()
@@ -113,6 +113,7 @@ def _render_header(self) -> None:
("💬 interact ", "Interact with a connected device"),
("🔍 analyze / an ", "Run Device Analyzer"),
("📜 logcat / lc ", "Start live logcat stream"),
+ ("🧹 clear", "Clear the terminal screen"),
("🚪 exit", "Quit Ghost Framework"),
("🔄 Index 99", "Return to Menu / Exit (UI helper)")
]
@@ -139,25 +140,32 @@ def _render_header(self) -> None:
self.rich.print()
def print_empty(self, message: str = "", end: str = "\n") -> None:
+ """Print a simple message."""
self.rich.print(message)
def print_information(self, message: str) -> None:
+ """Print an informational message in a panel."""
self.rich.print(Panel(Text(message), border_style=PURPLE, title="[bold white]INFO", box=box.MINIMAL))
def print_warning(self, message: str) -> None:
+ """Print a warning message in a panel."""
self.rich.print(Panel(Text(message), border_style="yellow", title="[bold white]WARNING", box=box.MINIMAL))
def print_error(self, message: str) -> None:
+ """Print an error message in a panel."""
self.rich.print(Panel(Text(message), border_style="red", title="[bold white]ERROR", box=box.MINIMAL))
def print_success(self, message: str) -> None:
+ """Print a success message in a panel."""
self.rich.print(Panel(Text(message), border_style="green", title="[bold white]SUCCESS", box=box.MINIMAL))
def print_usage(self, usage: str) -> None:
+ """Print usage information for a command."""
usage_text = Text.assemble(("Usage: ", "bold"), (usage, ""))
footer = Text("Index 99 → Return to Menu", style=INFO_STYLE)
self.rich.print(Panel(usage_text, border_style=PURPLE, title="[bold]USAGE", subtitle=footer))
def print_process(self, message: str) -> None:
+ """Show a processing spinner with a message."""
with self.rich.status(Text(message, style=INFO_STYLE), spinner="bouncingBall", spinner_style=PURPLE):
pass
@@ -173,25 +181,14 @@ def print_table(self, title: str, columns: tuple, *rows) -> None:
self.rich.print(wrapper)
def do_exit(self, _) -> None:
- """ Exit Ghost Framework.
-
- :return None: None
- :raises EOFError: EOF error
- """
-
+ """Quit Ghost Framework and disconnect all devices."""
for device in list(self.devices):
self.devices[device]['device'].disconnect()
del self.devices[device]
-
raise EOFError
def do_connect(self, args: list) -> None:
- """ Connect device.
-
- :param list args: arguments
- :return None: None
- """
-
+ """Connect to a device via ADB."""
if len(args) < 2:
self.print_usage("connect :[port]")
return
@@ -214,7 +211,6 @@ def do_connect(self, args: list) -> None:
}
})
self.print_empty("")
-
self.print_information(
f"Type %greendevices%end to list all connected devices.")
self.print_information(
@@ -223,37 +219,25 @@ def do_connect(self, args: list) -> None:
)
def do_devices(self, _) -> None:
- """ Show connected devices.
-
- :return None: None
- """
-
+ """List all connected devices."""
if not self.devices:
self.print_warning("No devices connected.")
return
devices = []
-
for device in self.devices:
devices.append(
(device, self.devices[device]['host'],
self.devices[device]['port']))
-
self.print_table("Connected Devices", ('ID', 'Host', 'Port'), *devices)
def do_disconnect(self, args: list) -> None:
- """ Disconnect device.
-
- :param list args: arguments
- :return None: None
- """
-
+ """Disconnect a connected device by ID."""
if len(args) < 2:
self.print_usage("disconnect ")
return
device_id = int(args[1])
-
if device_id not in self.devices:
self.print_error("Invalid device ID!")
return
@@ -262,29 +246,23 @@ def do_disconnect(self, args: list) -> None:
self.devices.pop(device_id)
def do_interact(self, args: list) -> None:
- """ Interact with device.
-
- :param list args: arguments
- :return None: None
- """
-
+ """Interact with a connected device by ID."""
if len(args) < 2:
self.print_usage("interact ")
return
device_id = int(args[1])
-
if device_id not in self.devices:
self.print_error("Invalid device ID!")
return
self.print_process(f"Interacting with device {str(device_id)}...")
self.devices[device_id]['device'].interact()
-
- def shell(self) -> None:
- """ Run console shell.
- :return None: None
- """
+ def do_clear(self, _) -> None:
+ """Clear the terminal screen."""
+ self.rich.clear()
+ def shell(self) -> None:
+ """Start the main Ghost Framework loop."""
self.loop()