Full security platform with web dashboard, 16 Flask blueprints, 26 modules, autonomous AI agent, WebUSB hardware support, and Archon Android companion app. Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
366 lines
13 KiB
Python
366 lines
13 KiB
Python
"""
|
|
Reverse Shell Manager - Manage incoming reverse shell connections from Archon companion app.
|
|
Control the RevShell listener, manage sessions, execute commands, transfer files.
|
|
"""
|
|
|
|
DESCRIPTION = "Reverse Shell — remote device management via Archon"
|
|
AUTHOR = "AUTARCH"
|
|
VERSION = "1.0"
|
|
CATEGORY = "offense"
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
|
|
class RevShellManager:
|
|
"""Interactive reverse shell management menu."""
|
|
|
|
def __init__(self):
|
|
from core.revshell import get_listener
|
|
self._get_listener = get_listener
|
|
|
|
@property
|
|
def listener(self):
|
|
return self._get_listener()
|
|
|
|
def show_menu(self):
|
|
li = self.listener
|
|
sessions = li.list_sessions()
|
|
alive = [s for s in sessions if s.get('alive', False)]
|
|
|
|
print(f"\n{'='*55}")
|
|
print(" Reverse Shell Manager")
|
|
print(f"{'='*55}")
|
|
print(f" Listener: {'RUNNING on ' + str(li.host) + ':' + str(li.port) if li.running else 'Stopped'}")
|
|
print(f" Sessions: {len(alive)} active, {len(sessions)} total")
|
|
if li.running:
|
|
print(f" Token: {li.auth_token}")
|
|
print()
|
|
print(" -- Listener --")
|
|
print(" 1) Start Listener")
|
|
print(" 2) Stop Listener")
|
|
print(" 3) Listener Status")
|
|
print()
|
|
print(" -- Sessions --")
|
|
print(" 10) List Sessions")
|
|
print(" 11) Select Session (interactive shell)")
|
|
print(" 12) Execute Command")
|
|
print(" 13) Disconnect Session")
|
|
print()
|
|
print(" -- Device Info --")
|
|
print(" 20) System Info")
|
|
print(" 21) Installed Packages")
|
|
print(" 22) Running Processes")
|
|
print(" 23) Network Connections")
|
|
print(" 24) Logcat Output")
|
|
print()
|
|
print(" -- Capture --")
|
|
print(" 30) Take Screenshot")
|
|
print(" 31) Download File")
|
|
print(" 32) Upload File")
|
|
print()
|
|
print(" 0) Back")
|
|
print()
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────────
|
|
|
|
def _pick_session(self, prompt=" Select session #: "):
|
|
"""Let user pick a session from the list."""
|
|
sessions = self.listener.list_sessions()
|
|
alive = [s for s in sessions if s.get('alive', False)]
|
|
if not alive:
|
|
print(" No active sessions.")
|
|
return None
|
|
print("\n Active Sessions:")
|
|
for i, s in enumerate(alive, 1):
|
|
uptime_m = s.get('uptime', 0) // 60
|
|
print(f" {i}) [{s['session_id'][:8]}] {s['device']} "
|
|
f"(Android {s['android']}, UID {s['uid']}) — {uptime_m}m")
|
|
try:
|
|
choice = int(input(prompt).strip())
|
|
if 1 <= choice <= len(alive):
|
|
return alive[choice - 1]['session_id']
|
|
except (ValueError, EOFError, KeyboardInterrupt):
|
|
pass
|
|
return None
|
|
|
|
def _get_session_obj(self, sid):
|
|
"""Get the actual session object."""
|
|
session = self.listener.get_session(sid)
|
|
if not session or not session.alive:
|
|
print(f" Session {sid} not found or dead.")
|
|
return None
|
|
return session
|
|
|
|
# ── Listener ────────────────────────────────────────────────────
|
|
|
|
def do_start(self):
|
|
if self.listener.running:
|
|
print(" Listener already running.")
|
|
return
|
|
try:
|
|
host = input(f" Bind address [0.0.0.0]: ").strip() or '0.0.0.0'
|
|
port_s = input(f" Port [17322]: ").strip() or '17322'
|
|
token = input(f" Auth token (blank=random): ").strip() or None
|
|
except (EOFError, KeyboardInterrupt):
|
|
return
|
|
|
|
from core.revshell import start_listener
|
|
ok, msg = start_listener(host=host, port=int(port_s), token=token)
|
|
if ok:
|
|
print(f" {msg}")
|
|
print(f" Token: {self.listener.auth_token}")
|
|
else:
|
|
print(f" Error: {msg}")
|
|
|
|
def do_stop(self):
|
|
if not self.listener.running:
|
|
print(" Listener not running.")
|
|
return
|
|
from core.revshell import stop_listener
|
|
stop_listener()
|
|
print(" Listener stopped.")
|
|
|
|
def do_status(self):
|
|
li = self.listener
|
|
print(f"\n Listener Status:")
|
|
print(f" Running: {li.running}")
|
|
print(f" Host: {li.host}")
|
|
print(f" Port: {li.port}")
|
|
print(f" Token: {li.auth_token}")
|
|
sessions = li.list_sessions()
|
|
alive = [s for s in sessions if s.get('alive', False)]
|
|
print(f" Sessions: {len(alive)} active, {len(sessions)} total")
|
|
|
|
# ── Sessions ────────────────────────────────────────────────────
|
|
|
|
def do_list_sessions(self):
|
|
sessions = self.listener.list_sessions()
|
|
if not sessions:
|
|
print("\n No sessions.")
|
|
return
|
|
print(f"\n {'ID':<14} {'Device':<20} {'Android':<10} {'UID':<6} {'Uptime':<10} {'Cmds':<6} {'Status'}")
|
|
print(f" {'-'*80}")
|
|
for s in sessions:
|
|
uptime_m = s.get('uptime', 0) // 60
|
|
status = 'ALIVE' if s.get('alive') else 'DEAD'
|
|
print(f" {s['session_id']:<14} {s['device']:<20} {s['android']:<10} "
|
|
f"{s['uid']:<6} {uptime_m}m{'':<7} {s.get('commands_executed', 0):<6} {status}")
|
|
|
|
def do_interactive_shell(self):
|
|
sid = self._pick_session()
|
|
if not sid:
|
|
return
|
|
session = self._get_session_obj(sid)
|
|
if not session:
|
|
return
|
|
|
|
print(f"\n Interactive shell — {session.device_name} (Android {session.android_version})")
|
|
print(f" Type 'exit' or Ctrl+C to leave.\n")
|
|
|
|
while session.alive:
|
|
try:
|
|
cmd = input(f" {session.device_name}$ ").strip()
|
|
except (EOFError, KeyboardInterrupt):
|
|
print()
|
|
break
|
|
if not cmd:
|
|
continue
|
|
if cmd.lower() in ('exit', 'quit'):
|
|
break
|
|
|
|
result = session.execute(cmd, timeout=30)
|
|
if result['stdout']:
|
|
for line in result['stdout'].rstrip('\n').split('\n'):
|
|
print(f" {line}")
|
|
if result['stderr']:
|
|
for line in result['stderr'].rstrip('\n').split('\n'):
|
|
print(f" [stderr] {line}")
|
|
if result['exit_code'] != 0:
|
|
print(f" [exit code: {result['exit_code']}]")
|
|
|
|
def do_execute_command(self):
|
|
sid = self._pick_session()
|
|
if not sid:
|
|
return
|
|
session = self._get_session_obj(sid)
|
|
if not session:
|
|
return
|
|
try:
|
|
cmd = input(" Command: ").strip()
|
|
timeout_s = input(" Timeout [30]: ").strip() or '30'
|
|
except (EOFError, KeyboardInterrupt):
|
|
return
|
|
if not cmd:
|
|
return
|
|
|
|
print(f" Executing on {session.device_name}...")
|
|
result = session.execute(cmd, timeout=int(timeout_s))
|
|
if result['stdout']:
|
|
print(f"\n --- stdout ---")
|
|
for line in result['stdout'].rstrip('\n').split('\n'):
|
|
print(f" {line}")
|
|
if result['stderr']:
|
|
print(f"\n --- stderr ---")
|
|
for line in result['stderr'].rstrip('\n').split('\n'):
|
|
print(f" {line}")
|
|
print(f"\n Exit code: {result['exit_code']}")
|
|
|
|
def do_disconnect_session(self):
|
|
sid = self._pick_session(" Session to disconnect #: ")
|
|
if not sid:
|
|
return
|
|
self.listener.remove_session(sid)
|
|
print(f" Session {sid} disconnected.")
|
|
|
|
# ── Device Info ─────────────────────────────────────────────────
|
|
|
|
def _run_special(self, label, method_name, **kwargs):
|
|
sid = self._pick_session()
|
|
if not sid:
|
|
return
|
|
session = self._get_session_obj(sid)
|
|
if not session:
|
|
return
|
|
print(f" Fetching {label} from {session.device_name}...")
|
|
method = getattr(session, method_name)
|
|
result = method(**kwargs)
|
|
if result.get('exit_code', -1) == 0:
|
|
output = result.get('stdout', '')
|
|
if output:
|
|
for line in output.rstrip('\n').split('\n'):
|
|
print(f" {line}")
|
|
else:
|
|
print(f" (no output)")
|
|
else:
|
|
print(f" Error: {result.get('stderr', 'Failed')}")
|
|
|
|
def do_sysinfo(self):
|
|
self._run_special("system info", "sysinfo")
|
|
|
|
def do_packages(self):
|
|
self._run_special("packages", "packages")
|
|
|
|
def do_processes(self):
|
|
self._run_special("processes", "processes")
|
|
|
|
def do_netstat(self):
|
|
self._run_special("network connections", "netstat")
|
|
|
|
def do_logcat(self):
|
|
try:
|
|
lines = input(" Lines [100]: ").strip() or '100'
|
|
except (EOFError, KeyboardInterrupt):
|
|
return
|
|
sid = self._pick_session()
|
|
if not sid:
|
|
return
|
|
session = self._get_session_obj(sid)
|
|
if not session:
|
|
return
|
|
print(f" Fetching logcat ({lines} lines) from {session.device_name}...")
|
|
result = session.dumplog(lines=int(lines))
|
|
if result.get('exit_code', -1) == 0:
|
|
output = result.get('stdout', '')
|
|
if output:
|
|
for line in output.rstrip('\n').split('\n'):
|
|
print(f" {line}")
|
|
else:
|
|
print(f" Error: {result.get('stderr', 'Failed')}")
|
|
|
|
# ── Capture ─────────────────────────────────────────────────────
|
|
|
|
def do_screenshot(self):
|
|
sid = self._pick_session()
|
|
if not sid:
|
|
return
|
|
print(f" Taking screenshot...")
|
|
filepath = self.listener.save_screenshot(sid)
|
|
if filepath:
|
|
print(f" Saved: {filepath}")
|
|
else:
|
|
print(f" Screenshot failed.")
|
|
|
|
def do_download(self):
|
|
sid = self._pick_session()
|
|
if not sid:
|
|
return
|
|
try:
|
|
remote_path = input(" Remote file path: ").strip()
|
|
except (EOFError, KeyboardInterrupt):
|
|
return
|
|
if not remote_path:
|
|
return
|
|
print(f" Downloading {remote_path}...")
|
|
filepath = self.listener.save_download(sid, remote_path)
|
|
if filepath:
|
|
print(f" Saved: {filepath}")
|
|
else:
|
|
print(f" Download failed.")
|
|
|
|
def do_upload(self):
|
|
sid = self._pick_session()
|
|
if not sid:
|
|
return
|
|
try:
|
|
local_path = input(" Local file path: ").strip()
|
|
remote_path = input(" Remote destination: ").strip()
|
|
except (EOFError, KeyboardInterrupt):
|
|
return
|
|
if not local_path or not remote_path:
|
|
return
|
|
if not Path(local_path).exists():
|
|
print(f" Local file not found: {local_path}")
|
|
return
|
|
|
|
session = self._get_session_obj(sid)
|
|
if not session:
|
|
return
|
|
print(f" Uploading to {remote_path}...")
|
|
result = session.upload(local_path, remote_path)
|
|
if result.get('exit_code', -1) == 0:
|
|
print(f" Upload complete.")
|
|
else:
|
|
print(f" Error: {result.get('stderr', 'Failed')}")
|
|
|
|
# ── Main Loop ──────────────────────────────────────────────────
|
|
|
|
def run_interactive(self):
|
|
while True:
|
|
self.show_menu()
|
|
try:
|
|
choice = input(" Select > ").strip()
|
|
except (EOFError, KeyboardInterrupt):
|
|
break
|
|
if choice == '0':
|
|
break
|
|
|
|
actions = {
|
|
'1': self.do_start,
|
|
'2': self.do_stop,
|
|
'3': self.do_status,
|
|
'10': self.do_list_sessions,
|
|
'11': self.do_interactive_shell,
|
|
'12': self.do_execute_command,
|
|
'13': self.do_disconnect_session,
|
|
'20': self.do_sysinfo,
|
|
'21': self.do_packages,
|
|
'22': self.do_processes,
|
|
'23': self.do_netstat,
|
|
'24': self.do_logcat,
|
|
'30': self.do_screenshot,
|
|
'31': self.do_download,
|
|
'32': self.do_upload,
|
|
}
|
|
action = actions.get(choice)
|
|
if action:
|
|
action()
|
|
else:
|
|
print(" Invalid choice.")
|
|
|
|
|
|
def run():
|
|
mgr = RevShellManager()
|
|
mgr.run_interactive()
|