Additional Reverse Shell code cleanup

This commit is contained in:
2025-12-06 00:45:04 -07:00
parent 68dd46347b
commit cc8a1fade0
4 changed files with 26 additions and 155 deletions

View File

@@ -2,7 +2,6 @@
from __future__ import annotations
import asyncio
import os
import sys
import subprocess
from typing import Any, Dict, Optional
@@ -30,7 +29,6 @@ class PowershellChannel:
self._reader_task = None
self._writer_task = None
self._stdin_queue: asyncio.Queue = asyncio.Queue()
self._pty = None
self._proc: Optional[asyncio.subprocess.Process] = None
self._exit_code: Optional[int] = None
self._frame_cls = getattr(role, "_frame_cls", None)
@@ -70,13 +68,6 @@ class PowershellChannel:
# Keep the process alive and read commands from stdin; -Command - tells PS to consume stdin.
return [shell, "-NoLogo", "-NoProfile", "-NoExit", "-Command", "-"]
def _initial_size(self) -> tuple:
cols = int(self.metadata.get("cols") or self.metadata.get("columns") or 120) if isinstance(self.metadata, dict) else 120
rows = int(self.metadata.get("rows") or 32) if isinstance(self.metadata, dict) else 32
cols = max(20, min(cols, 300))
rows = max(10, min(rows, 200))
return cols, rows
# ------------------------------------------------------------------ Lifecycle
async def start(self) -> None:
if sys.platform.lower().startswith("win") is False:
@@ -85,25 +76,9 @@ class PowershellChannel:
return
argv = self._powershell_argv()
cols, rows = self._initial_size()
self.role._log(f"reverse_tunnel ps start channel={self.channel_id} argv={' '.join(argv)} cols={cols} rows={rows}")
self.role._log(f"reverse_tunnel ps start channel={self.channel_id} argv={' '.join(argv)} mode=pipes")
# Preferred: ConPTY via pywinpty.
try:
import pywinpty # type: ignore
self._pty = pywinpty.Process(
spawn_cmd=" ".join(argv[:-1]) if argv[-1] == "-" else " ".join(argv),
dimensions=(cols, rows),
)
self._reader_task = self.loop.create_task(self._pump_pty_stdout())
self._writer_task = self.loop.create_task(self._pump_pty_stdin())
self.role._log(f"reverse_tunnel ps channel started (pty) argv={' '.join(argv)} cols={cols} rows={rows}")
return
except Exception as exc:
self.role._log(f"reverse_tunnel ps channel pywinpty unavailable, falling back to pipes: {exc}", error=True)
# Fallback: subprocess pipes (no PTY).
# Pipes (no PTY).
try:
self._proc = await asyncio.create_subprocess_exec(
*argv,
@@ -119,7 +94,7 @@ class PowershellChannel:
self._reader_task = self.loop.create_task(self._pump_proc_stdout())
self._writer_task = self.loop.create_task(self._pump_proc_stdin())
self.role._log(f"reverse_tunnel ps channel started (pipes) argv={' '.join(argv)} cols={cols} rows={rows}")
self.role._log(f"reverse_tunnel ps channel started (pipes) argv={' '.join(argv)}")
async def on_frame(self, frame) -> None:
if self._closed:
@@ -139,80 +114,8 @@ class PowershellChannel:
return
async def _handle_control(self, payload: bytes) -> None:
try:
import json
data = json.loads(payload.decode("utf-8"))
except Exception:
return
cols = data.get("cols") or data.get("columns")
rows = data.get("rows")
if cols is None and rows is None:
return
try:
cols_int = int(cols) if cols is not None else None
rows_int = int(rows) if rows is not None else None
except Exception:
return
await self._resize(cols_int, rows_int)
async def _resize(self, cols: Optional[int], rows: Optional[int]) -> None:
# Resize only applies to PTY sessions; pipe mode ignores.
if self._pty is None:
return
try:
cur_cols, cur_rows = self._initial_size()
if cols is None:
cols = cur_cols
if rows is None:
rows = cur_rows
cols = max(20, min(int(cols), 300))
rows = max(10, min(int(rows), 200))
self._pty.set_size(cols, rows)
self.role._log(f"reverse_tunnel ps channel resized cols={cols} rows={rows}")
except Exception:
self.role._log("reverse_tunnel ps channel resize failed", error=True)
async def _pump_pty_stdout(self) -> None:
loop = asyncio.get_event_loop()
try:
while not self._closed and self._pty:
chunk = await loop.run_in_executor(None, self._pty.read, 4096)
if chunk is None:
break
data = chunk.encode("utf-8", errors="replace") if isinstance(chunk, str) else bytes(chunk)
if not data:
break
frame = self._make_frame(MSG_DATA, payload=data)
await self._send_frame(frame)
except asyncio.CancelledError:
pass
except Exception:
self.role._log("reverse_tunnel ps pty stdout pump error", error=True)
finally:
await self.stop(reason="stdout_closed")
async def _pump_pty_stdin(self) -> None:
loop = asyncio.get_event_loop()
try:
while not self._closed and self._pty:
try:
data = await self._stdin_queue.get()
except asyncio.CancelledError:
break
if data is None:
break
text = data.decode("utf-8", errors="replace") if isinstance(data, (bytes, bytearray)) else str(data)
try:
await loop.run_in_executor(None, self._pty.write, text)
except Exception:
break
except asyncio.CancelledError:
pass
except Exception:
self.role._log("reverse_tunnel ps pty stdin pump error", error=True)
finally:
await self.stop(reason="stdin_closed")
# No-op for pipe mode; resize is not supported here.
return
# -------------------- Pipe fallback pumps --------------------
async def _pump_proc_stdout(self) -> None:
@@ -258,11 +161,6 @@ class PowershellChannel:
if self._closed:
return
self._closed = True
if self._pty is not None:
try:
self._pty.terminate()
except Exception:
pass
if self._proc is not None:
try:
self._proc.terminate()

View File

@@ -27,7 +27,6 @@ pywinauto # Windows-based Macro Automation Library
sounddevice
numpy
pywin32; platform_system == "Windows"
pywinpty; platform_system == "Windows" # ConPTY bridge for reverse tunnel PowerShell sessions
# Ansible Libraries
ansible-core