diff --git a/ctrlsrv.py b/ctrlsrv.py new file mode 100755 index 0000000..cd7fcd3 --- /dev/null +++ b/ctrlsrv.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 +# coding=utf-8 +# pyvncs +# Copyright (C) 2017-2018 Matias Fernandez +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +import pyvncs +from lib import log +from lib import common +from argparse import ArgumentParser +from threading import Thread +import time +import sys +import socket +import signal +import readline +import traceback + +#_debug = log.debug +_debug = print + +if common.isWindows(): + _debug("Wintendo...") + import win32ts + import win32security + import win32con + import win32api + import ntsecuritycon + import win32process + import win32event + +def signal_handler(signal, frame): + _debug("Exiting on signal %s..." % signal) + sys.exit(0) + +signal.signal(signal.SIGINT, signal_handler) + +config = { +} + +class ControlThread(Thread): + def __init__(self, threads): + Thread.__init__(self) + self.threads = threads + self.setDaemon(True) + + def run(self): + # elimina los threads muertos + while True: + time.sleep(1) + for t in threads: + if not t.isAlive(): + _debug("ControlThread removing dead", t) + threads.remove(t) + +class VNCThread(Thread): + def __init__(self, port, password): + Thread.__init__(self) + self.ip = None + self.port = port + self.sock = None + self.password = password + self.setDaemon(True) + + def __del__(self): + _debug("VNCThread died") + + + def run(self): + + TCP_IP = '0.0.0.0' + _debug("[+] Listen...") + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.sock.bind((TCP_IP, int(self.port))) + self.sock.listen(4) + (conn, (ip,port)) = self.sock.accept() + + _debug("[+] New server socket started for " + ip + ":" + str(port)) + #_debug("Thread", self) + server = pyvncs.server.VncServer(conn, self.password) + #server.CONFIG._8bitdither = CONFIG._8bitdither + status = server.init() + + if not status: + _debug("Error negotiating client init") + return False + server.protocol() + + +class ClientThread(Thread): + def __init__(self, sock, ip, port, config): + Thread.__init__(self) + self.ip = ip + self.port = port + self.sock = sock + self.setDaemon(True) + self.config = config + + def __del__(self): + _debug("ClientThread died") + + def run(self): + #_debug("[+] New server socket thread started for " + self.ip + ":" + str(self.port)) + #_debug("Thread", self) + f = self.sock.makefile('rw') + + f.write("AUTH:>") + f.flush() + passwd = f.readline().strip("\n") + if passwd != config["PASSWORD"]: + time.sleep(1) + f.write("!NO AUTH") + f.flush() + _debug("NO AUTH '%s' != '%s'" % (passwd, config["PASSWORD"])) + self.sock.close() + return + + while True: + f.write("OK:>") + f.flush() + try: + data = f.readline() + cmd = data.strip() + if not data: break + + if cmd == "_DEBUG": + sys.stdout = sys.stderr = f + f.write("OK\n") + + elif cmd == "PING": + f.write("PONG\n") + + elif cmd == "QUIT": + f.write("BYE\n") + self.sock.close() + return + + elif cmd.startswith("STARTVNC"): + params = cmd.split() + if len(params) != 3: + f.write("!NOT_PARAMS\n") + f.flush() + continue + _debug("START VNC !!!") + newthread = VNCThread(params[1], params[2]) + newthread.setDaemon(True) + newthread.start() + + elif cmd == "_WINSESSIONS" and common.isWindows(): + winsessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE) + print(winsessions, file=f) + + elif cmd == "_WINCONSOLE" and common.isWindows(): + winsessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE) + print(winsessions, file=f) + active = win32ts.WTSGetActiveConsoleSessionId() + print(active, file=f) + token = win32ts.WTSQueryUserToken(active) + print(token, file=f) + duplicated = win32security.DuplicateTokenEx(token, win32con.MAXIMUM_ALLOWED, win32con.NULL, win32security.TokenPrimary, win32security.SECURITY_ATTRIBUTES()) + print("duplicated", token, file=f) + + elif cmd == "_TEST" and common.isWindows(): + winsessions = win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE) + print(winsessions, file=f) + active = win32ts.WTSGetActiveConsoleSessionId() + print(active, file=f) + token = win32ts.WTSQueryUserToken(active) + print("token", token, file=f) + ntoken = win32security.DuplicateTokenEx(token, 3 , win32con.MAXIMUM_ALLOWED , win32security.TokenPrimary , win32security.SECURITY_ATTRIBUTES() ) + print("ntoken", ntoken, file=f) + #th = win32security.OpenProcessToken(win32api.GetCurrentProcess(), win32con.MAXIMUM_ALLOWED) + #print("th", th, file=f) + shell_as2(ntoken, True, "cmd") + + elif cmd == "_EVAL": + while True: + f.write("EV:>") + f.flush() + data = f.readline() + cmd = data.strip() + if cmd == "": continue + if cmd == "_QUIT": break + if not data: break + + try: + eval(data) + except: + print("ERROR:", sys.exc_info()[0], file=f) + print(traceback.format_exc(), file=f) + f.flush() + + _debug("eval:", cmd.strip()) + f.flush() + + elif cmd == "_ERROR": + a = 1/0 + + else: + f.write("!NO_CMD %s\n" % cmd) + + _debug("command:", cmd.strip()) + f.flush() + except: + print("ERROR:", sys.exc_info()[0], file=f) + print(traceback.format_exc(), file=f) + f.flush() + +def get_all_privs(th): + # Try to give ourselves some extra privs (only works if we're admin): + # SeBackupPrivilege - so we can read anything + # SeDebugPrivilege - so we can find out about other processes (otherwise OpenProcess will fail for some) + # SeSecurityPrivilege - ??? what does this do? + + # Problem: Vista+ support "Protected" processes, e.g. audiodg.exe. We can't see info about these. + # Interesting post on why Protected Process aren't really secure anyway: http://www.alex-ionescu.com/?p=34 + + privs = win32security.GetTokenInformation(th, ntsecuritycon.TokenPrivileges) + for privtuple in privs: + privs2 = win32security.GetTokenInformation(th, ntsecuritycon.TokenPrivileges) + newprivs = [] + for privtuple2 in privs2: + if privtuple2[0] == privtuple[0]: + newprivs.append((privtuple2[0], 2)) # SE_PRIVILEGE_ENABLED + else: + newprivs.append((privtuple2[0], privtuple2[1])) + + # Adjust privs + privs3 = tuple(newprivs) + win32security.AdjustTokenPrivileges(th, False, privs3) +def shell_as(th, enable_privs = 0): + #t = thread(th) + #print(t.as_text()) + new_tokenh = win32security.DuplicateTokenEx(th, 3 , win32con.MAXIMUM_ALLOWED , win32security.TokenPrimary , win32security.SECURITY_ATTRIBUTES() ) + print("new_tokenh: %s" % new_tokenh) + print("Impersonating...") + if enable_privs: + get_all_privs(new_tokenh) + commandLine = "cmd" + si = win32process.STARTUPINFO() + print("pysecdump: Starting shell with required privileges...") + (hProcess, hThread, dwProcessId, dwThreadId) = win32process.CreateProcessAsUser( + new_tokenh, + None, # AppName + commandLine, # Command line + None, # Process Security + None, # ThreadSecurity + 1, # Inherit Handles? + win32process.NORMAL_PRIORITY_CLASS, + None, # New environment + None, # Current directory + si) # startup info. + win32event.WaitForSingleObject( hProcess, win32event.INFINITE ); + print("pysecdump: Quitting") + +def shell_as2(new_tokenh, enable_privs = 0, commandLine = "cmd"): + print("new_tokenh: %s" % new_tokenh) + print("Impersonating...") + if enable_privs: + get_all_privs(new_tokenh) + si = win32process.STARTUPINFO() + print("pysecdump: Starting shell with required privileges...") + (hProcess, hThread, dwProcessId, dwThreadId) = win32process.CreateProcessAsUser( + new_tokenh, + None, # AppName + commandLine, # Command line + None, # Process Security + None, # ThreadSecurity + 1, # Inherit Handles? + win32process.NORMAL_PRIORITY_CLASS, + None, # New environment + None, # Current directory + si) # startup info. + win32event.WaitForSingleObject( hProcess, win32event.INFINITE ) + print("pysecdump: Quitting") + +def main(argv): + global threads, config + parser = ArgumentParser() + parser.add_argument("-l", "--listen-address", dest="TCP_IP", + help="Listen in this address, default: %s" % ("0.0.0.0"), required=False, default='0.0.0.0') + parser.add_argument("-p", "--port", dest="TCP_PORT", + help="Listen in this port, default: %s" % ("5899"), type=int, required=False, default='5899') + parser.add_argument("-P", "--password", help="Sets password", required=True, dest="PASSWORD") + + args = parser.parse_args() + + config["PASSWORD"] = args.PASSWORD + config["PORT"] = args.TCP_PORT + + sockServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sockServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sockServer.bind((args.TCP_IP, args.TCP_PORT)) + + controlthread = ControlThread(threads) + controlthread.start() + threads.append(controlthread) + + #_debug("Multithreaded Python server : Waiting for connections from TCP clients...") + _debug("Runing on:", sys.platform) + while True: + sockServer.listen(4) + (conn, (ip,port)) = sockServer.accept() + newthread = ClientThread(conn, ip, port, config) + newthread.setDaemon(True) + newthread.start() + threads.append(newthread) + #print(threads) + + + +if __name__ == "__main__": + threads = [] + main(sys.argv) diff --git a/lib/common.py b/lib/common.py new file mode 100644 index 0000000..6af18f8 --- /dev/null +++ b/lib/common.py @@ -0,0 +1,68 @@ +# coding=utf-8 +# pyvncs +# Copyright (C) 2017-2018 Matias Fernandez +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +import sys +from multiprocessing import Process, Value +import subprocess +import os +import time +import mmap +import psutil + +def isWindows(): + return sys.platform.startswith("win") + +def isOSX(): + return sys.platform.startswith("darwin") + +def isLinux(): + return sys.platform.startswith("linux") + +class proc: + def __init__(self): + self.pid = Value('i', 0) + self._process = None + + def __del__(self): + pass + + def _setpid(self, pid): + self.pid.value = pid + + def getpid(self): + return self.pid.value + + def _newproc(self, cmd): + pr = subprocess.Popen(cmd) + #print("Launched forkproc Process ID:", str(pr.pid)) + self._setpid(pr.pid) + + def run(self, *cmd): + self._process = Process(target=self._newproc, args=(cmd)) + self._process.start() + self._process.join() + return self.pid.value + + def terminate(self): + if psutil.pid_exists(self.pid.value): + p = psutil.Process(self.pid.value) + p.terminate() + self._process.terminate() + + def waitproc(self): + while psutil.pid_exists(self.pid): + time.sleep(.25) diff --git a/lib/const.py b/lib/const.py new file mode 100644 index 0000000..235e4c5 --- /dev/null +++ b/lib/const.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# pyvncs +# Copyright (C) 2017-2018 Matias Fernandez +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +import sys + +# constants workaround +class _const: + class ConstError(TypeError): pass + def __setattr__(self, name, value): + if name in self.__dict__.keys(): + raise (self.ConstError, "Can't rebind const(%s)" % name) + self.__dict__[name]=value + +sys.modules[__name__]=_const() + diff --git a/lib/encodings/common.py b/lib/encodings/common.py new file mode 100644 index 0000000..948fa94 --- /dev/null +++ b/lib/encodings/common.py @@ -0,0 +1,19 @@ +# coding=utf-8 +# pyvncs +# Copyright (C) 2017-2018 Matias Fernandez +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +class ENCODINGS: + pass \ No newline at end of file diff --git a/lib/encodings/cursor.py b/lib/encodings/cursor.py new file mode 100644 index 0000000..37cb736 --- /dev/null +++ b/lib/encodings/cursor.py @@ -0,0 +1,20 @@ +# coding=utf-8 +# pyvncs +# Copyright (C) 2017-2018 Matias Fernandez +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +from . import common + +common.ENCODINGS.cursor = -239 diff --git a/lib/encodings/raw.py b/lib/encodings/raw.py new file mode 100644 index 0000000..85bfcc8 --- /dev/null +++ b/lib/encodings/raw.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# pyvncs +# Copyright (C) 2017-2018 Matias Fernandez +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +from . import common +from struct import * + +def send_image(x, y, w, h, image): + _buff = bytearray() + rectangles = 1 + _buff.extend(pack("!BxH", 0, rectangles)) + _buff.extend(pack("!HHHH", x, y, w, h)) + _buff.extend(pack(">i", common.ENCODINGS.raw)) + _buff.extend( image.tobytes() ) + + return _buff + +common.ENCODINGS.raw = 0 +common.ENCODINGS.raw_send_image = send_image \ No newline at end of file diff --git a/lib/log.py b/lib/log.py new file mode 100644 index 0000000..5c43b42 --- /dev/null +++ b/lib/log.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# pyvncs +# Copyright (C) 2017-2018 Matias Fernandez +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +import logging +logging.basicConfig(level=logging.DEBUG, format='[%(threadName)s] %(message)s') +logger = logging.getLogger('pyvncs') + + +def debug(*args): + str = "" + for s in args: + str = "%s %s" % (str, s) + str = str.strip() + logger.debug(str) diff --git a/pyvncs/__pycache__/server.cpython-36.pyc b/pyvncs/__pycache__/server.cpython-36.pyc index 96d4937..a87805a 100644 Binary files a/pyvncs/__pycache__/server.cpython-36.pyc and b/pyvncs/__pycache__/server.cpython-36.pyc differ diff --git a/pyvncs/server.py b/pyvncs/server.py index da7d137..8c5c215 100644 --- a/pyvncs/server.py +++ b/pyvncs/server.py @@ -29,6 +29,7 @@ import zlib import numpy as np from lib.encodings import * +from lib import log def hexdump(data): str = "" @@ -182,7 +183,7 @@ class VncServer(): def __del__(self): - print("VncServer died") + log.debug("VncServer died") def encrypt(self, key, data): k = des(key, ECB, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5) @@ -221,7 +222,7 @@ class VncServer(): data = sock.recv(1024) except socket.timeout: data = None - print("getbuff() timeout") + log.debug("getbuff() timeout") return data @@ -232,15 +233,15 @@ class VncServer(): # RFB version handshake data = self.getbuff(30) - print("init received: '%s'" % data) + log.debug("init received: '%s'" % data) server_version = float(self.RFB_VERSION) try: client_version = float(data[4:11]) except: - print("Error parsing client version") + log.debug("Error parsing client version") return False - print("client, server:", client_version, server_version) + log.debug("client, server:", client_version, server_version) # security types handshake sendbuff = pack("B", len(self.sectypes)) # number of security types @@ -254,13 +255,13 @@ class VncServer(): sectype = None if sectype not in self.sectypes: - print("Incompatible security type: %s" % data) + log.debug("Incompatible security type: %s" % data) sock.send(pack("B", 1)) # failed handshake self.sendmessage("Incompatible security type") sock.close() return False - print("sec type data: %s" % data) + log.debug("sec type data: %s" % data) # VNC Auth if sectype == 2: @@ -278,9 +279,9 @@ class VncServer(): if data == crypted: # Handshake successful sock.send(pack("I", 0)) - print("Auth OK") + log.debug("Auth OK") else: - print("Invalid auth") + log.debug("Invalid auth") return False #unsupported VNC auth type @@ -289,7 +290,7 @@ class VncServer(): # get ClientInit data = self.getbuff(30) - print("Clientinit (shared flag)", repr(data)) + log.debug("Clientinit (shared flag)", repr(data)) self.ServerInit() @@ -300,9 +301,9 @@ class VncServer(): sock = self.socket screen = ImageGrab.grab() - print("screen", repr(screen)) + log.debug("screen", repr(screen)) size = screen.size - print("size", repr(size)) + log.debug("size", repr(size)) del screen width = size[0] @@ -339,8 +340,8 @@ class VncServer(): sendbuff += pack("!I", desktop_name_len) sendbuff += desktop_name.encode() - print("width", repr(width)) - print("height", repr(height)) + log.debug("width", repr(width)) + log.debug("height", repr(height)) sock.send(sendbuff) @@ -437,7 +438,7 @@ class VncServer(): kbdmap[0xffe2] = keyboard.Key.shift while True: - #print(".", end='', flush=True) + #log.debug(".", end='', flush=True) r,_,_ = select.select([self.socket],[],[],0) if r == []: #no data @@ -448,10 +449,10 @@ class VncServer(): try: data = sock.recv(1) # read first byte except socket.timeout: - #print("timeout") + #log.debug("timeout") continue except Exception as e: - print("exception '%s'" % e) + log.debug("exception '%s'" % e) sock.close() break @@ -462,40 +463,40 @@ class VncServer(): if data[0] == 0: # client SetPixelFormat data2 = sock.recv(19, socket.MSG_WAITALL) - print("Client Message Type: Set Pixel Format (0)") + log.debug("Client Message Type: Set Pixel Format (0)") (self.bpp, self.depth, self.bigendian, self.truecolor, self.red_maximum, self.green_maximum, self.blue_maximum, self.red_shift, self.green_shift, self.blue_shift ) = unpack("!xxxBBBBHHHBBBxxx", data2) - print("IMG bpp, depth, endian, truecolor", self.bpp, self.depth, self.bigendian, self.truecolor) - print("SHIFTS", self.red_shift, self.green_shift, self.blue_shift) - print("MAXS", self.red_maximum, self.green_maximum, self.blue_maximum) + log.debug("IMG bpp, depth, endian, truecolor", self.bpp, self.depth, self.bigendian, self.truecolor) + log.debug("SHIFTS", self.red_shift, self.green_shift, self.blue_shift) + log.debug("MAXS", self.red_maximum, self.green_maximum, self.blue_maximum) if self.red_shift > self.blue_shift: self.primaryOrder = "rgb" else: self.primaryOrder = "bgr" - print("Using order: ", self.primaryOrder) + log.debug("Using order: ", self.primaryOrder) continue if data[0] == 2: # SetEncoding data2 = sock.recv(3) - print("Client Message Type: SetEncoding (2)") + log.debug("Client Message Type: SetEncoding (2)") (nencodings,) = unpack("!xH", data2) - print("SetEncoding: total encodings", repr(nencodings)) + log.debug("SetEncoding: total encodings", repr(nencodings)) data2 = sock.recv(4 * nencodings, socket.MSG_WAITALL) - #print("len", len(data2)) + #log.debug("len", len(data2)) self.client_encodings = unpack("!%si" % nencodings, data2) - #print("data", repr(self.client_encodings), len(self.client_encodings)) + #log.debug("data", repr(self.client_encodings), len(self.client_encodings)) if hasattr(enc.ENCODINGS, "cursor") and enc.ENCODINGS.cursor in self.client_encodings: - print("Remote cursor encoding present") + log.debug("Remote cursor encoding present") self.remotecursor = True self.cursorchanged = True if hasattr(enc.ENCODINGS, "zlib") and enc.ENCODINGS.zlib in self.client_encodings: - print("Using zlib encoding") + log.debug("Using zlib encoding") self.encoding = enc.ENCODINGS.zlib continue @@ -503,10 +504,10 @@ class VncServer(): if data[0] == 3: # FBUpdateRequest data2 = sock.recv(9, socket.MSG_WAITALL) - #print("Client Message Type: FBUpdateRequest (3)") + #log.debug("Client Message Type: FBUpdateRequest (3)") #print(len(data2)) (incremental, x, y, w, h) = unpack("!BHHHH", data2) - #print("RFBU:", incremental, x, y, w, h) + #log.debug("RFBU:", incremental, x, y, w, h) self.SendRectangles(sock, x, y, w, h, incremental) if self.remotecursor and self.cursorchanged: @@ -519,7 +520,7 @@ class VncServer(): data2 = sock.recv(7) # B = U8, L = U32 (downflag, key) = unpack("!BxxL", data2) - print("KeyEvent", downflag, hex(key)) + log.debug("KeyEvent", downflag, hex(key)) # special key if key in kbdmap: @@ -531,9 +532,9 @@ class VncServer(): kbdkey = None try: - print("KEY:", kbdkey) + log.debug("KEY:", kbdkey) except: - print("KEY: (unprintable)") + log.debug("KEY: (unprintable)") try: if downflag: @@ -541,7 +542,7 @@ class VncServer(): else: keyboard.Controller().release(kbdkey) except: - print("Error sending key") + log.debug("Error sending key") continue @@ -557,46 +558,46 @@ class VncServer(): mouse.Controller().position = (x, y) if buttons[0] and not left_pressed: - print("LEFT PRESSED") + log.debug("LEFT PRESSED") mouse.Controller().press(mouse.Button.left) left_pressed = 1 elif not buttons[0] and left_pressed: - print("LEFT RELEASED") + log.debug("LEFT RELEASED") mouse.Controller().release(mouse.Button.left) left_pressed = 0 if buttons[1] and not middle_pressed: - print("MIDDLE PRESSED") + log.debug("MIDDLE PRESSED") mouse.Controller().press(mouse.Button.middle) middle_pressed = 1 elif not buttons[1] and middle_pressed: - print("MIDDLE RELEASED") + log.debug("MIDDLE RELEASED") mouse.Controller().release(mouse.Button.middle) middle_pressed = 0 if buttons[2] and not right_pressed: - print("RIGHT PRESSED") + log.debug("RIGHT PRESSED") mouse.Controller().press(mouse.Button.right) right_pressed = 1 elif not buttons[2] and right_pressed: - print("RIGHT RELEASED") + log.debug("RIGHT RELEASED") mouse.Controller().release(mouse.Button.right) right_pressed = 0 if buttons[3]: - print("SCROLLUP PRESSED") + log.debug("SCROLLUP PRESSED") mouse.Controller().scroll(0, 2) if buttons[4]: - print("SCROLLDOWN PRESSED") + log.debug("SCROLLDOWN PRESSED") mouse.Controller().scroll(0, -2) - #print("PointerEvent", buttonmask, x, y) + #log.debug("PointerEvent", buttonmask, x, y) continue else: data2 = sock.recv(4096) - print("RAW Server received data:", repr(data[0]) , data+data2) + log.debug("RAW Server received data:", repr(data[0]) , data+data2) def GetRectangle(self, x, y, w, h): @@ -621,7 +622,7 @@ class VncServer(): def SendRectangles(self, sock, x, y, w, h, incremental=0): # send FramebufferUpdate to client - #print("start SendRectangles") + #log.debug("start SendRectangles") rectangle = self.GetRectangle(x, y, w, h) if not rectangle: rectangle = Image.new("RGB", [w, h], (0,0,0)) @@ -650,7 +651,7 @@ class VncServer(): (x, y, _, _) = diff.getbbox() w = rectangle.width h = rectangle.height - #print("XYWH:", x,y,w,h, "diff", repr(diff.getbbox())) + #log.debug("XYWH:", x,y,w,h, "diff", repr(diff.getbbox())) stimeout = sock.gettimeout() sock.settimeout(None) @@ -677,7 +678,7 @@ class VncServer(): #redMask = ((1 << redBits) - 1) << self.red_shift #greenMask = ((1 << greenBits) - 1) << self.green_shift #blueMask = ((1 << blueBits) - 1) << self.blue_shift - #print("redMask", redMask, greenMask, blueMask) + #log.debug("redMask", redMask, greenMask, blueMask) if self.primaryOrder == "bgr": self.blue_shift = 0 @@ -757,7 +758,7 @@ class VncServer(): sendbuff.extend(pack("!HHHH", x, y, w, h)) sendbuff.extend(pack(">i", self.encoding)) - print("Compressing...") + log.debug("Compressing...") zlibdata = compress.compress( image.tobytes() ) zlibdata += compress.flush() l = pack("!I", len(zlibdata) ) @@ -769,7 +770,7 @@ class VncServer(): # send with RAW encoding sendbuff.extend(enc.ENCODINGS.raw_send_image(x, y, w, h, image)) else: - print("[!] Unsupported BPP: %s" % self.bpp) + log.debug("[!] Unsupported BPP: %s" % self.bpp) self.framebuffer = lastshot try: @@ -778,4 +779,4 @@ class VncServer(): # connection closed? return False sock.settimeout(stimeout) - #print("end SendRectangles") + #log.debug("end SendRectangles") diff --git a/requeriments.txt b/requeriments.txt index 50504bc..dbfde75 100644 --- a/requeriments.txt +++ b/requeriments.txt @@ -1,2 +1,4 @@ -pyuserinput +pydes +pynput +numpy diff --git a/server.py b/server.py index 0a6afa2..02a9f95 100755 --- a/server.py +++ b/server.py @@ -5,9 +5,19 @@ import pyvncs from argparse import ArgumentParser from threading import Thread from time import sleep - import sys import socket +import signal +from lib import log + +#_debug = log.debug +_debug = print + +def signal_handler(signal, frame): + _debug("Exiting on %s signal..." % signal) + sys.exit(0) + +signal.signal(signal.SIGINT, signal_handler) class ControlThread(Thread): @@ -22,7 +32,7 @@ class ControlThread(Thread): sleep(1) for t in threads: if not t.isAlive(): - print("ControlThread removing dead", t) + _debug("ControlThread removing dead", t) threads.remove(t) class ClientThread(Thread): @@ -34,17 +44,17 @@ class ClientThread(Thread): self.setDaemon(True) def __del__(self): - print("ClientThread died") + _debug("ClientThread died") def run(self): - print("[+] New server socket thread started for " + self.ip + ":" + str(self.port)) - #print("Thread", self) + _debug("[+] New server socket thread started for " + self.ip + ":" + str(self.port)) + #_debug("Thread", self) server = pyvncs.server.VncServer(self.sock, VNC_PASSWORD) server.CONFIG._8bitdither = CONFIG._8bitdither status = server.init() if not status: - print("Error negotiating client init") + _debug("Error negotiating client init") return False server.protocol() @@ -61,11 +71,17 @@ def main(argv): help="Listen in this port, default: %s" % ("5901"), type=int, required=False, default='5901') parser.add_argument("-P", "--password", help="Sets password", required=True, dest="VNC_PASSWORD") parser.add_argument("-8", "--8bitdither", help="Enable 8 bit dithering", required=False, action='store_true', dest="dither") + parser.add_argument("-O", "--output-file", help="Redirects all debug output to file", required=False, dest="OUTFILE") + args = parser.parse_args() - + + if args.OUTFILE is not None: + fsock = open(args.OUTFILE, 'w') + sys.stdout = sys.stderr = fsock + # Multithreaded Python server TCP_IP = '0.0.0.0' if not hasattr(args,"TCP_IP") else args.TCP_IP - TCP_PORT = '0.0.0.0' if not hasattr(args,"TCP_PORT") else args.TCP_PORT + TCP_PORT = '5901' if not hasattr(args,"TCP_PORT") else args.TCP_PORT VNC_PASSWORD = args.VNC_PASSWORD CONFIG._8bitdither = args.dither @@ -77,8 +93,8 @@ def main(argv): controlthread.start() threads.append(controlthread) - print("Multithreaded Python server : Waiting for connections from TCP clients...") - print("Runing on:", sys.platform) + _debug("Multithreaded Python server : Waiting for connections from TCP clients...") + _debug("Runing on:", sys.platform) while True: sockServer.listen(4) (conn, (ip,port)) = sockServer.accept() @@ -95,8 +111,8 @@ if __name__ == "__main__": main(sys.argv) except KeyboardInterrupt: # quit - print("Exiting on ctrl+c...") + _debug("Exiting on ctrl+c...") #for t in threads: - # print("Killing", t) + # _debug("Killing", t) sys.exit() \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..52c7033 --- /dev/null +++ b/test.py @@ -0,0 +1,16 @@ +from lib import common +import time + +if __name__ == '__main__': + #c = ["/bin/ls", "-alh"] + #c = ["/bin/sleep", "5"] + c = ["./test.sh"] + r = common.proc() + r.run(c) + print("PID", r.getpid()) + #print("Waiting...") + #r.waitproc() + time.sleep(1) + r.terminate() + del r + print("DONE") diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..9c56fd9 --- /dev/null +++ b/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +for a in $(seq 1 5); do +echo "$a $$" +sleep 1 +done + diff --git a/winservice.py b/winservice.py new file mode 100644 index 0000000..dea87ec --- /dev/null +++ b/winservice.py @@ -0,0 +1,79 @@ +import win32service +import win32serviceutil +import win32api +import win32con +import win32event +import win32evtlogutil +import servicemanager +import os +import sys +from lib import const +from lib import common +from win32api import OutputDebugString as ODS +import traceback + + +const.SERVICENAME = "Test Service" +const.SERVICEDNAME = "Test Service" +const.SERVICEDESC = "Test Service Description" + +const.CHILD = [ + "C:\\Program Files\\Python36\\python.exe", + "C:\\pyvncs\\ctrlsrv.py", + "-P", + "kaka80" +] + +class service(win32serviceutil.ServiceFramework): + + _svc_name_ = const.SERVICENAME + _svc_display_name_ = const.SERVICEDNAME + _svc_description_ = const.SERVICEDESC + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) + + def SvcStop(self): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + win32event.SetEvent(self.hWaitStop) + + def SvcDoRun(self): + servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STARTED,(self._svc_name_, '')) + self.timeout = 3000 + + servicemanager.LogInfoMsg("%s - is running 1" % const.SERVICENAME) + r = common.proc() + try: + r.run(const.CHILD) + except: + servicemanager.LogInfoMsg("ERROR: %s" % sys.exc_info()[0]) + servicemanager.LogInfoMsg(traceback.format_exc()) + sys.exit(1) + + newpid = r.getpid() + servicemanager.LogInfoMsg("%s - started child with pid %s" % (const.SERVICENAME, newpid)) + + while True: + # Wait for service stop signal, if I timeout, loop again + rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout) + # Check to see if self.hWaitStop happened + if rc == win32event.WAIT_OBJECT_0: + # Stop signal encountered + servicemanager.LogInfoMsg("%s - STOPPED" % const.SERVICENAME) + r.terminate() + break + #else: + # servicemanager.LogInfoMsg("%s - still running" % const.SERVICENAME) + + +def ctrlHandler(ctrlType): + return True + +if __name__ == '__main__': + ODS("__main__\n") + servicemanager.LogInfoMsg("TEST") + appdir = os.path.abspath(os.path.dirname(sys.argv[0])) + os.chdir(appdir) + win32api.SetConsoleCtrlHandler(ctrlHandler, True) + win32serviceutil.HandleCommandLine(service)