Merge branch 'experimental' into 'master'

Experimental

See merge request radixcl/pyvncs!1
This commit is contained in:
Matias Fernandez Lillo
2019-07-30 02:57:12 +00:00
23 changed files with 1411 additions and 423 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__/
*.py[cod]

327
ctrlsrv.py Executable file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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)

260
lib/bgr233_palette.py Normal file
View File

@@ -0,0 +1,260 @@
# BGR233 palette
palette = [
0, 0, 0,
36, 0, 0,
73, 0, 0,
109, 0, 0,
146, 0, 0,
182, 0, 0,
219, 0, 0,
255, 0, 0,
0, 36, 0,
36, 36, 0,
73, 36, 0,
109, 36, 0,
146, 36, 0,
182, 36, 0,
219, 36, 0,
255, 36, 0,
0, 73, 0,
36, 73, 0,
73, 73, 0,
109, 73, 0,
146, 73, 0,
182, 73, 0,
219, 73, 0,
255, 73, 0,
0, 109, 0,
36, 109, 0,
73, 109, 0,
109, 109, 0,
146, 109, 0,
182, 109, 0,
219, 109, 0,
255, 109, 0,
0, 146, 0,
36, 146, 0,
73, 146, 0,
109, 146, 0,
146, 146, 0,
182, 146, 0,
219, 146, 0,
255, 146, 0,
0, 182, 0,
36, 182, 0,
73, 182, 0,
109, 182, 0,
146, 182, 0,
182, 182, 0,
219, 182, 0,
255, 182, 0,
0, 219, 0,
36, 219, 0,
73, 219, 0,
109, 219, 0,
146, 219, 0,
182, 219, 0,
219, 219, 0,
255, 219, 0,
0, 255, 0,
36, 255, 0,
73, 255, 0,
109, 255, 0,
146, 255, 0,
182, 255, 0,
219, 255, 0,
255, 255, 0,
0, 0, 85,
36, 0, 85,
73, 0, 85,
109, 0, 85,
146, 0, 85,
182, 0, 85,
219, 0, 85,
255, 0, 85,
0, 36, 85,
36, 36, 85,
73, 36, 85,
109, 36, 85,
146, 36, 85,
182, 36, 85,
219, 36, 85,
255, 36, 85,
0, 73, 85,
36, 73, 85,
73, 73, 85,
109, 73, 85,
146, 73, 85,
182, 73, 85,
219, 73, 85,
255, 73, 85,
0, 109, 85,
36, 109, 85,
73, 109, 85,
109, 109, 85,
146, 109, 85,
182, 109, 85,
219, 109, 85,
255, 109, 85,
0, 146, 85,
36, 146, 85,
73, 146, 85,
109, 146, 85,
146, 146, 85,
182, 146, 85,
219, 146, 85,
255, 146, 85,
0, 182, 85,
36, 182, 85,
73, 182, 85,
109, 182, 85,
146, 182, 85,
182, 182, 85,
219, 182, 85,
255, 182, 85,
0, 219, 85,
36, 219, 85,
73, 219, 85,
109, 219, 85,
146, 219, 85,
182, 219, 85,
219, 219, 85,
255, 219, 85,
0, 255, 85,
36, 255, 85,
73, 255, 85,
109, 255, 85,
146, 255, 85,
182, 255, 85,
219, 255, 85,
255, 255, 85,
0, 0, 170,
36, 0, 170,
73, 0, 170,
109, 0, 170,
146, 0, 170,
182, 0, 170,
219, 0, 170,
255, 0, 170,
0, 36, 170,
36, 36, 170,
73, 36, 170,
109, 36, 170,
146, 36, 170,
182, 36, 170,
219, 36, 170,
255, 36, 170,
0, 73, 170,
36, 73, 170,
73, 73, 170,
109, 73, 170,
146, 73, 170,
182, 73, 170,
219, 73, 170,
255, 73, 170,
0, 109, 170,
36, 109, 170,
73, 109, 170,
109, 109, 170,
146, 109, 170,
182, 109, 170,
219, 109, 170,
255, 109, 170,
0, 146, 170,
36, 146, 170,
73, 146, 170,
109, 146, 170,
146, 146, 170,
182, 146, 170,
219, 146, 170,
255, 146, 170,
0, 182, 170,
36, 182, 170,
73, 182, 170,
109, 182, 170,
146, 182, 170,
182, 182, 170,
219, 182, 170,
255, 182, 170,
0, 219, 170,
36, 219, 170,
73, 219, 170,
109, 219, 170,
146, 219, 170,
182, 219, 170,
219, 219, 170,
255, 219, 170,
0, 255, 170,
36, 255, 170,
73, 255, 170,
109, 255, 170,
146, 255, 170,
182, 255, 170,
219, 255, 170,
255, 255, 170,
0, 0, 255,
36, 0, 255,
73, 0, 255,
109, 0, 255,
146, 0, 255,
182, 0, 255,
219, 0, 255,
255, 0, 255,
0, 36, 255,
36, 36, 255,
73, 36, 255,
109, 36, 255,
146, 36, 255,
182, 36, 255,
219, 36, 255,
255, 36, 255,
0, 73, 255,
36, 73, 255,
73, 73, 255,
109, 73, 255,
146, 73, 255,
182, 73, 255,
219, 73, 255,
255, 73, 255,
0, 109, 255,
36, 109, 255,
73, 109, 255,
109, 109, 255,
146, 109, 255,
182, 109, 255,
219, 109, 255,
255, 109, 255,
0, 146, 255,
36, 146, 255,
73, 146, 255,
109, 146, 255,
146, 146, 255,
182, 146, 255,
219, 146, 255,
255, 146, 255,
0, 182, 255,
36, 182, 255,
73, 182, 255,
109, 182, 255,
146, 182, 255,
182, 182, 255,
219, 182, 255,
255, 182, 255,
0, 219, 255,
36, 219, 255,
73, 219, 255,
109, 219, 255,
146, 219, 255,
182, 219, 255,
219, 219, 255,
255, 219, 255,
0, 255, 255,
36, 255, 255,
73, 255, 255,
109, 255, 255,
146, 255, 255,
182, 255, 255,
219, 255, 255,
255, 255, 255
]

68
lib/common.py Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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)

29
lib/const.py Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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()

View File

@@ -16,5 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# at least, raw encoding is needed by the rfb protocol
from . import common as enc
from . import common
from . import raw
from . import zlib
from . import cursor

29
lib/encodings/common.py Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
encodings = {}
class ENCODINGS:
raw = 0
zlib = 6
# supported pseudo-encodings
cursor = -239
encodings_priority = [
ENCODINGS.zlib,
ENCODINGS.raw
]

36
lib/encodings/cursor.py Normal file
View File

@@ -0,0 +1,36 @@
# 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 <http://www.gnu.org/licenses/>.
from . import common
from struct import *
from lib import log
import zlib
class Encoding:
name = 'Cursor'
id = -239
description = 'Cursor pseudo encoding'
enabled = True
pseudoEncoding = True
cursor_sent = False
def __init__(self):
log.debug("Initialized", __name__)
common.encodings[common.ENCODINGS.cursor] = Encoding
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))

46
lib/encodings/raw.py Normal file
View File

@@ -0,0 +1,46 @@
# 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 <http://www.gnu.org/licenses/>.
from . import common
from struct import *
from lib import log
class Encoding:
_buff = None
name = 'raw'
id = 0
description = 'Raw VNC encoding'
enabled = True
firstUpdateSent = False
def __init__(self):
log.debug("Initialized", __name__)
def send_image(self, x, y, w, h, image):
self._buff = bytearray()
rectangles = 1
self._buff.extend(pack("!BxH", 0, rectangles)) # message type 0 == FramebufferUpdate
self._buff.extend(pack("!HHHH", x, y, w, h))
self._buff.extend(pack(">i", self.id))
self._buff.extend( image.tobytes() )
return self._buff
common.encodings[common.ENCODINGS.raw] = Encoding
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))

54
lib/encodings/zlib.py Normal file
View File

@@ -0,0 +1,54 @@
from . import common
from struct import *
from lib import log
import zlib
class Encoding:
name = 'zlib'
id = 6
description = 'zlib VNC encoding'
enabled = True
firstUpdateSent = False
_compressObj = None
def __init__(self):
log.debug("Initialized", __name__)
self._compressObj = zlib.compressobj(
zlib.Z_DEFAULT_COMPRESSION, # level: 0-9
zlib.DEFLATED, # method: must be DEFLATED
zlib.MAX_WBITS, # window size in bits:
# -15..-8: negate, suppress header
# 8..15: normal
# 16..30: subtract 16, gzip header
zlib.DEF_MEM_LEVEL, # mem level: 1..8/9
zlib.Z_DEFAULT_STRATEGY # strategy:
# 0 = Z_DEFAULT_STRATEGY
# 1 = Z_FILTERED
# 2 = Z_HUFFMAN_ONLY
# 3 = Z_RLE
# 4 = Z_FIXED
)
def send_image(self, x, y, w, h, image):
sendbuff = bytearray()
rectangles = 1
sendbuff.extend(pack("!BxH", 0, rectangles)) # message type 0 == FramebufferUpdate
sendbuff.extend(pack("!HHHH", x, y, w, h))
sendbuff.extend(pack(">i", self.id))
#log.debug("Compressing...")
zlibdata = self._compressObj.compress( image.tobytes() )
zlibdata += self._compressObj.flush(zlib.Z_FULL_FLUSH)
#log.debug("LEN", len(zlibdata))
l = pack("!I", len(zlibdata) )
sendbuff.extend( l ) # send length
sendbuff.extend( zlibdata ) # send compressed data
return sendbuff
common.encodings[common.ENCODINGS.zlib] = Encoding
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))

41
lib/imagegrab.py Normal file
View File

@@ -0,0 +1,41 @@
import sys
from PIL import Image
from lib import log
if sys.platform == "linux" or sys.platform == "linux2":
log.debug("ImageGrab: running on Linux")
from Xlib import display, X
# take screen images, that's not the best way, so here
# we use directly use xlib to take the screenshot.
class ImageGrab():
def grab():
dsp = display.Display()
root = dsp.screen().root
geom = root.get_geometry()
w = geom.width
h = geom.height
raw = root.get_image(0, 0, w ,h, X.ZPixmap, 0xffffffff)
image = Image.frombytes("RGB", (w, h), raw.data, "raw", "BGRX")
return image
elif sys.platform == "darwin":
log.debug("ImageGrab: running on darwin")
import Quartz.CoreGraphics as CG
class ImageGrab():
def grab():
screenshot = CG.CGWindowListCreateImage(CG.CGRectInfinite, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault)
width = CG.CGImageGetWidth(screenshot)
height = CG.CGImageGetHeight(screenshot)
bytesperrow = CG.CGImageGetBytesPerRow(screenshot)
pixeldata = CG.CGDataProviderCopyData(CG.CGImageGetDataProvider(screenshot))
i = Image.frombytes("RGBA", (width, height), pixeldata)
(b, g, r, x) = i.split()
i = Image.merge("RGBX", (r, g, b, x))
return i
else:
log.debug("ImageGrab: running on Unknown!")
from PIL import ImageGrab

47
lib/kbdctrl.py Normal file
View File

@@ -0,0 +1,47 @@
import sys
from struct import pack, unpack
from pynput import keyboard
from lib import log
from lib.kbdmap import *
class KeyboardController:
kbdmap = kbdmap
kbdkey = ''
downflag = None
key = None
controller = None
kbd = None
def __init__(self):
self.kbd = keyboard
self.controller = self.kbd.Controller()
def process_event(self, data):
# B = U8, L = U32
(self.downflag, self.key) = unpack("!BxxL", data)
log.debug("KeyEvent", self.downflag, hex(self.key))
# special key
if self.key in self.kbdmap:
self.kbdkey = self.kbdmap[self.key]
log.debug("SPECIAL KEY", self.kbdkey)
else: # normal key
try:
self.kbdkey = self.kbd.KeyCode.from_char(chr(self.key))
except:
self.kbdkey = None
# debug keypress to stdout
try:
log.debug("KEY:", self.kbdkey)
except:
log.debug("KEY: (unprintable)")
# send the actual keyboard event
try:
if self.downflag:
self.controller.press(self.kbdkey)
else:
self.controller.release(self.kbdkey)
except:
log.debug("Error sending key")

79
lib/kbdmap.py Normal file
View File

@@ -0,0 +1,79 @@
import sys
from pynput import keyboard
__all__ = ['kbdmap']
kbdmap = {
0xff08: keyboard.Key.backspace,
0xff09: keyboard.Key.tab,
0xff0d: keyboard.Key.enter,
0xff1b: keyboard.Key.esc,
0xff63: keyboard.Key.insert if hasattr(keyboard.Key, "insert") else None,
0xffff: keyboard.Key.delete,
0xff50: keyboard.Key.home,
0xff57: keyboard.Key.end,
0xff55: keyboard.Key.page_up,
0xff56: keyboard.Key.page_down,
0xff51: keyboard.Key.left,
0xff52: keyboard.Key.up,
0xff53: keyboard.Key.right,
0xff54: keyboard.Key.down,
0xffbe: keyboard.Key.f1,
0xffbf: keyboard.Key.f2,
0xffc0: keyboard.Key.f3,
0xffc1: keyboard.Key.f4,
0xffc2: keyboard.Key.f5,
0xffc3: keyboard.Key.f6,
0xffc4: keyboard.Key.f7,
0xffc5: keyboard.Key.f8,
0xffc6: keyboard.Key.f9,
0xffc7: keyboard.Key.f10,
0xffc8: keyboard.Key.f11,
0xffc9: keyboard.Key.f12,
0xffca: keyboard.Key.f13,
0xffcb: keyboard.Key.f14,
0xffcc: keyboard.Key.f15,
0xffcd: keyboard.Key.f16,
0xffce: keyboard.Key.f17,
0xffcf: keyboard.Key.f18,
0xffd0: keyboard.Key.f19,
0xffd1: keyboard.Key.f20,
0xffe1: keyboard.Key.shift_l,
0xffe2: keyboard.Key.shift_r,
0xffe3: keyboard.Key.ctrl_l,
0xffe4: keyboard.Key.ctrl_r,
0xffe7: None, # "KEY_MetaLeft"
0xffe8: None, # "KEY_MetaRight"
#0xffe9: keyboard.Key.cmd_l,
0xffe9: keyboard.Key.alt,
0xffea: keyboard.Key.alt_gr, # "KEY_AltRight"
0xff14: keyboard.Key.scroll_lock if hasattr(keyboard.Key, "scroll_lock") else None,
0xff15: keyboard.Key.print_screen if hasattr(keyboard.Key, "print_screen") else None, # "KEY_Sys_Req"
0xff7f: keyboard.Key.num_lock if hasattr(keyboard.Key, "num_lock") else None,
0xffe5: keyboard.Key.caps_lock,
0xff13: keyboard.Key.pause if hasattr(keyboard.Key, "pause") else None,
0xffeb: keyboard.Key.cmd_r, # "KEY_Super_L"
0xffec: keyboard.Key.cmd_r, # "KEY_Super_R"
0xffed: None, # "KEY_Hyper_L"
0xffee: None, # "KEY_Hyper_R"
0xffb0: None, # "KEY_KP_0"
0xffb1: None, # "KEY_KP_1"
0xffb2: None, # "KEY_KP_2"
0xffb3: None, # "KEY_KP_3"
0xffb4: None, # "KEY_KP_4"
0xffb5: None, # "KEY_KP_5"
0xffb6: None, # "KEY_KP_6"
0xffb7: None, # "KEY_KP_7"
0xffb8: None, # "KEY_KP_8"
0xffb9: None, # "KEY_KP_9"
0xff8d: None, # "KEY_KP_Enter"
0x002f: "/", # KEY_ForwardSlash
0x005c: "\\", # KEY_BackSlash
0x0020: keyboard.Key.space, # "KEY_SpaceBar"
0xff7e: keyboard.Key.alt_gr, # altgr, at least on a mac (?)
#0xfe03: keyboard.Key.alt_l,
0xfe03: keyboard.Key.cmd_l,
}
if sys.platform == "darwin":
kbdmap[0xffe2] = keyboard.Key.shift

28
lib/log.py Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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)

60
lib/mousectrl.py Normal file
View File

@@ -0,0 +1,60 @@
from struct import pack, unpack
from pynput import mouse
from lib import log
class MouseController():
def __init__(self):
self.buttonmask = 0
self.buttons = [0, 0, 0, 0, 0, 0, 0, 0]
self.left_pressed = 0
self.right_pressed = 0
self.middle_pressed = 0
def process_event(self, data):
(self.buttonmask, x, y) = unpack("!BHH", data)
self.buttons[0] = self.buttonmask & int("0000001", 2) # left button
self.buttons[1] = self.buttonmask & int("0000010", 2) # middle button
self.buttons[2] = self.buttonmask & int("0000100", 2) # right button
self.buttons[3] = self.buttonmask & int("0001000", 2) # scroll up
self.buttons[4] = self.buttonmask & int("0010000", 2) # scroll down
# set mouse position
mouse.Controller().position = (x, y)
# process mouse button events
if self.buttons[0] and not self.left_pressed:
log.debug("LEFT PRESSED")
mouse.Controller().press(mouse.Button.left)
self.left_pressed = 1
elif not self.buttons[0] and self.left_pressed:
log.debug("LEFT RELEASED")
mouse.Controller().release(mouse.Button.left)
self.left_pressed = 0
if self.buttons[1] and not self.middle_pressed:
log.debug("MIDDLE PRESSED")
mouse.Controller().press(mouse.Button.middle)
self.middle_pressed = 1
elif not self.buttons[1] and self.middle_pressed:
log.debug("MIDDLE RELEASED")
mouse.Controller().release(mouse.Button.middle)
self.middle_pressed = 0
if self.buttons[2] and not self.right_pressed:
log.debug("RIGHT PRESSED")
mouse.Controller().press(mouse.Button.right)
self.right_pressed = 1
elif not self.buttons[2] and self.right_pressed:
log.debug("RIGHT RELEASED")
mouse.Controller().release(mouse.Button.right)
self.right_pressed = 0
if self.buttons[3]:
log.debug("SCROLLUP PRESSED")
mouse.Controller().scroll(0, 2)
if self.buttons[4]:
log.debug("SCROLLDOWN PRESSED")
mouse.Controller().scroll(0, -2)
#log.debug("PointerEvent", buttonmask, x, y)

31
lib/oshelpers/windows.py Normal file
View File

@@ -0,0 +1,31 @@
import ctypes
import sys
from elevate import elevate
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
def run_as_admin_old(argv=None):
shell32 = ctypes.windll.shell32
if argv is None and shell32.IsUserAnAdmin():
return True
if argv is None:
argv = sys.argv
if hasattr(sys, '_MEIPASS'):
# Support pyinstaller wrapped program.
arguments = argv[1:]
else:
arguments = argv
argument_line = u' '.join(arguments)
executable = sys.executable
ret = shell32.ShellExecuteW(None, u"runas", executable, argument_line, None, 1)
if int(ret) <= 32:
return False
return None
def run_as_admin():
elevate()

View File

@@ -19,16 +19,24 @@ from struct import *
from pyDes import *
from time import sleep
from pynput import mouse, keyboard
from PIL import Image, ImageChops, ImageDraw, ImagePalette
import socket
import select
import os
import sys
import random
import zlib
import numpy as np
from lib.encodings import *
from lib import mousectrl
from lib import kbdctrl
from lib.imagegrab import ImageGrab
from lib import log
from lib import bgr233_palette
# encodings support
import lib.encodings as encs
from lib.encodings.common import ENCODINGS
def hexdump(data):
str = ""
@@ -38,7 +46,7 @@ def hexdump(data):
return str
def quantizetopalette(silf, palette, dither=False):
"""Convert an RGB or L mode image to use a given P image's palette."""
"""Converts an RGB or L mode image to use a given P image's palette."""
silf.load()
@@ -59,50 +67,13 @@ def quantizetopalette(silf, palette, dither=False):
except AttributeError:
return silf._makeself(im)
from PIL import Image, ImageChops, ImageDraw, ImagePalette
if sys.platform == "linux" or sys.platform == "linux2":
from Xlib import display, X
# take screen images, that's not the best way, so here
# we use directly use xlib to take the screenshot.
class ImageGrab():
def grab():
dsp = display.Display()
root = dsp.screen().root
geom = root.get_geometry()
w = geom.width
h = geom.height
raw = root.get_image(0, 0, w ,h, X.ZPixmap, 0xffffffff)
image = Image.frombytes("RGB", (w, h), raw.data, "raw", "BGRX")
return image
elif sys.platform == "darwin":
import Quartz.CoreGraphics as CG
class ImageGrab():
def grab():
screenshot = CG.CGWindowListCreateImage(CG.CGRectInfinite, CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID, CG.kCGWindowImageDefault)
width = CG.CGImageGetWidth(screenshot)
height = CG.CGImageGetHeight(screenshot)
bytesperrow = CG.CGImageGetBytesPerRow(screenshot)
pixeldata = CG.CGDataProviderCopyData(CG.CGImageGetDataProvider(screenshot))
i = Image.frombytes("RGBA", (width, height), pixeldata)
(b, g, r, x) = i.split()
i = Image.merge("RGBX", (r, g, b, x))
return i
else:
from PIL import ImageGrab
class VncServer():
class CONFIG:
_8bitdither = False
encoding_object = None
def __init__(self, socket, password):
self.RFB_VERSION = '003.008'
self.RFB_SECTYPES = [
@@ -114,75 +85,10 @@ class VncServer():
self.framebuffer = None
self.password = password
self.sectypes = self.RFB_SECTYPES
self.remotecursor = False
self.BGR233 = [0, 0, 0, 0, 0, 85, 0, 0, 170, 0, 0, 255, 36, 0, 0,
36, 0, 85, 36, 0, 170, 36, 0, 255, 73, 0, 0, 73, 0,
85, 73, 0, 170, 73, 0, 255, 109, 0, 0, 109, 0, 85,
109, 0, 170, 109, 0, 255, 146, 0, 0, 146, 0, 85, 146,
0, 170, 146, 0, 255, 182, 0, 0, 182, 0, 85, 182, 0,
170, 182, 0, 255, 219, 0, 0, 219, 0, 85, 219, 0, 170,
219, 0, 255, 255, 0, 0, 255, 0, 85, 255, 0, 170, 255,
0, 255, 0, 36, 0, 0, 36, 85, 0, 36, 170, 0, 36, 255,
36, 36, 0, 36, 36, 85, 36, 36, 170, 36, 36, 255, 73,
36, 0, 73, 36, 85, 73, 36, 170, 73, 36, 255, 109, 36,
0, 109, 36, 85, 109, 36, 170, 109, 36, 255, 146, 36,
0, 146, 36, 85, 146, 36, 170, 146, 36, 255, 182, 36,
0, 182, 36, 85, 182, 36, 170, 182, 36, 255, 219, 36,
0, 219, 36, 85, 219, 36, 170, 219, 36, 255, 255, 36,
0, 255, 36, 85, 255, 36, 170, 255, 36, 255, 0, 73, 0,
0, 73, 85, 0, 73, 170, 0, 73, 255, 36, 73, 0, 36, 73,
85, 36, 73, 170, 36, 73, 255, 73, 73, 0, 73, 73, 85,
73, 73, 170, 73, 73, 255, 109, 73, 0, 109, 73, 85,
109, 73, 170, 109, 73, 255, 146, 73, 0, 146, 73, 85,
146, 73, 170, 146, 73, 255, 182, 73, 0, 182, 73, 85,
182, 73, 170, 182, 73, 255, 219, 73, 0, 219, 73, 85,
219, 73, 170, 219, 73, 255, 255, 73, 0, 255, 73, 85,
255, 73, 170, 255, 73, 255, 0, 109, 0, 0, 109, 85, 0,
109, 170, 0, 109, 255, 36, 109, 0, 36, 109, 85, 36,
109, 170, 36, 109, 255, 73, 109, 0, 73, 109, 85, 73,
109, 170, 73, 109, 255, 109, 109, 0, 109, 109, 85, 109,
109, 170, 109, 109, 255, 146, 109, 0, 146, 109, 85,
146, 109, 170, 146, 109, 255, 182, 109, 0, 182, 109,
85, 182, 109, 170, 182, 109, 255, 219, 109, 0, 219,
109, 85, 219, 109, 170, 219, 109, 255, 255, 109, 0,
255, 109, 85, 255, 109, 170, 255, 109, 255, 0, 146, 0,
0, 146, 85, 0, 146, 170, 0, 146, 255, 36, 146, 0, 36,
146, 85, 36, 146, 170, 36, 146, 255, 73, 146, 0, 73,
146, 85, 73, 146, 170, 73, 146, 255, 109, 146, 0, 109,
146, 85, 109, 146, 170, 109, 146, 255, 146, 146, 0, 146,
146, 85, 146, 146, 170, 146, 146, 255, 182, 146, 0, 182,
146, 85, 182, 146, 170, 182, 146, 255, 219, 146, 0, 219,
146, 85, 219, 146, 170, 219, 146, 255, 255, 146, 0, 255,
146, 85, 255, 146, 170, 255, 146, 255, 0, 182, 0, 0,
182, 85, 0, 182, 170, 0, 182, 255, 36, 182, 0, 36, 182,
85, 36, 182, 170, 36, 182, 255, 73, 182, 0, 73, 182,
85, 73, 182, 170, 73, 182, 255, 109, 182, 0, 109, 182,
85, 109, 182, 170, 109, 182, 255, 146, 182, 0, 146,
182, 85, 146, 182, 170, 146, 182, 255, 182, 182, 0, 182,
182, 85, 182, 182, 170, 182, 182, 255, 219, 182, 0, 219,
182, 85, 219, 182, 170, 219, 182, 255, 255, 182, 0, 255,
182, 85, 255, 182, 170, 255, 182, 255, 0, 219, 0, 20,
219, 85, 0, 219, 170, 0, 219, 255, 36, 219, 0, 36, 219,
85, 36, 219, 170, 36, 219, 255, 73, 219, 0, 73, 219,
85, 73, 219, 170, 73, 219, 255, 109, 219, 0, 109, 219,
85, 109, 219, 170, 109, 219, 255, 146, 219, 0, 146, 219,
85, 146, 219, 170, 146, 219, 255, 182, 219, 0, 182, 219,
85, 182, 219, 170, 182, 219, 255, 219, 219, 0, 219, 219,
85, 219, 219, 170, 219, 219, 255, 255, 219, 0, 255, 219,
85, 255, 219, 170, 255, 219, 255, 0, 255, 0, 0, 255, 85,
0, 255, 170, 0, 255, 255, 36, 255, 0, 36, 255, 85, 36,
255, 170, 36, 255, 255, 73, 255, 0, 73, 255, 85, 73,
255, 170, 73, 255, 255, 109, 255, 0, 109, 255, 85, 109,
255, 170, 109, 255, 255, 146, 255, 0, 146, 255, 85,
146, 255, 170, 146, 255, 255, 182, 255, 0, 182, 255, 85,
182, 255, 170, 182, 255, 255, 219, 255, 0, 219, 255, 85,
219, 255, 170, 219, 255, 255, 255, 255, 0, 255, 255, 85,
255, 255, 170, 255, 255, 255]
self.cursor_support = False
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 +127,7 @@ class VncServer():
data = sock.recv(1024)
except socket.timeout:
data = None
print("getbuff() timeout")
log.debug("getbuff() timeout")
return data
@@ -232,15 +138,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 +160,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 +184,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 +195,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 +206,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,105 +245,29 @@ 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)
def protocol(self):
self.socket.settimeout(None) # set nonblocking socket
screen = ImageGrab.grab()
size = screen.size
width = size[0]
height = size[1]
del screen
mousecontroller = mousectrl.MouseController()
kbdcontroller = kbdctrl.KeyboardController()
self.primaryOrder = "rgb"
self.encoding = enc.ENCODINGS.raw
buttonmask = 0
buttons = [0, 0, 0, 0, 0, 0, 0, 0]
left_pressed = 0
right_pressed = 0
middle_pressed = 0
self.encoding = ENCODINGS.raw
self.encoding_object = encs.common.encodings[self.encoding]()
kbdmap = {
0xff08: keyboard.Key.backspace,
0xff09: keyboard.Key.tab,
0xff0d: keyboard.Key.enter,
0xff1b: keyboard.Key.esc,
0xff63: keyboard.Key.insert if hasattr(keyboard.Key, "insert") else None,
0xffff: keyboard.Key.delete,
0xff50: keyboard.Key.home,
0xff57: keyboard.Key.end,
0xff55: keyboard.Key.page_up,
0xff56: keyboard.Key.page_down,
0xff51: keyboard.Key.left,
0xff52: keyboard.Key.up,
0xff53: keyboard.Key.right,
0xff54: keyboard.Key.down,
0xffbe: keyboard.Key.f1,
0xffbf: keyboard.Key.f2,
0xffc0: keyboard.Key.f3,
0xffc1: keyboard.Key.f4,
0xffc2: keyboard.Key.f5,
0xffc3: keyboard.Key.f6,
0xffc4: keyboard.Key.f7,
0xffc5: keyboard.Key.f8,
0xffc6: keyboard.Key.f9,
0xffc7: keyboard.Key.f10,
0xffc8: keyboard.Key.f11,
0xffc9: keyboard.Key.f12,
0xffca: keyboard.Key.f13,
0xffcb: keyboard.Key.f14,
0xffcc: keyboard.Key.f15,
0xffcd: keyboard.Key.f16,
0xffce: keyboard.Key.f17,
0xffcf: keyboard.Key.f18,
0xffd0: keyboard.Key.f19,
0xffd1: keyboard.Key.f20,
0xffe1: keyboard.Key.shift_l,
0xffe2: keyboard.Key.shift_r,
0xffe3: keyboard.Key.ctrl_l,
0xffe4: keyboard.Key.ctrl_r,
0xffe7: None, # "KEY_MetaLeft"
0xffe8: None, # "KEY_MetaRight"
0xffe9: keyboard.Key.cmd_l,
0xffea: keyboard.Key.alt_gr, # "KEY_AltRight"
0xff14: keyboard.Key.scroll_lock if hasattr(keyboard.Key, "scroll_lock") else None,
0xff15: keyboard.Key.print_screen if hasattr(keyboard.Key, "print_screen") else None, # "KEY_Sys_Req"
0xff7f: keyboard.Key.num_lock if hasattr(keyboard.Key, "num_lock") else None,
0xffe5: keyboard.Key.caps_lock,
0xff13: keyboard.Key.pause if hasattr(keyboard.Key, "pause") else None,
0xffeb: keyboard.Key.cmd_r, # "KEY_Super_L"
0xffec: keyboard.Key.cmd_r, # "KEY_Super_R"
0xffed: None, # "KEY_Hyper_L"
0xffee: None, # "KEY_Hyper_R"
0xffb0: None, # "KEY_KP_0"
0xffb1: None, # "KEY_KP_1"
0xffb2: None, # "KEY_KP_2"
0xffb3: None, # "KEY_KP_3"
0xffb4: None, # "KEY_KP_4"
0xffb5: None, # "KEY_KP_5"
0xffb6: None, # "KEY_KP_6"
0xffb7: None, # "KEY_KP_7"
0xffb8: None, # "KEY_KP_8"
0xffb9: None, # "KEY_KP_9"
0xff8d: None, # "KEY_KP_Enter"
0x002f: "/", # KEY_ForwardSlash
0x005c: "\\", # KEY_BackSlash
0x0020: keyboard.Key.space, # "KEY_SpaceBar"
0xff7e: keyboard.Key.alt_gr, # altgr, at least on a mac (?)
0xfe03: keyboard.Key.alt_l,
}
if sys.platform == "darwin":
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 +278,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,141 +292,75 @@ 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("client_encodings", 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")
self.remotecursor = True
self.cursorchanged = True
if hasattr(enc.ENCODINGS, "zlib") and enc.ENCODINGS.zlib in self.client_encodings:
print("Using zlib encoding")
self.encoding = enc.ENCODINGS.zlib
# cursor support?
if ENCODINGS.cursor in self.client_encodings:
log.debug("client cursor support")
self.cursor_support = True
# which pixel encoding to use?
log.debug("encs.common.encodings_priority", encs.common.encodings_priority)
for e in encs.common.encodings_priority:
log.debug("E", e)
if e in self.client_encodings:
if self.encoding == e:
# don't initialize same encoding again
break
self.encoding = e
log.debug("Using %s encoding" % self.encoding)
self.encoding_object = encs.common.encodings[self.encoding]()
break
continue
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:
# FIXME: send cursor to remote client
self.cursorchanged = False
continue
if data[0] == 4:
kbdkey = ''
data2 = sock.recv(7)
# B = U8, L = U32
(downflag, key) = unpack("!BxxL", data2)
print("KeyEvent", downflag, hex(key))
# special key
if key in kbdmap:
kbdkey = kbdmap[key]
else: # normal key
try:
kbdkey = keyboard.KeyCode.from_char(chr(key))
except:
kbdkey = None
try:
print("KEY:", kbdkey)
except:
print("KEY: (unprintable)")
try:
if downflag:
keyboard.Controller().press(kbdkey)
else:
keyboard.Controller().release(kbdkey)
except:
print("Error sending key")
if data[0] == 4: # keyboard event
kbdcontroller.process_event(sock.recv(7))
continue
if data[0] == 5: # PointerEvent
data2 = sock.recv(5, socket.MSG_WAITALL)
(buttonmask, x, y) = unpack("!BHH", data2)
buttons[0] = buttonmask & int("0000001", 2) # left button
buttons[1] = buttonmask & int("0000010", 2) # middle button
buttons[2] = buttonmask & int("0000100", 2) # right button
buttons[3] = buttonmask & int("0001000", 2) # scroll up
buttons[4] = buttonmask & int("0010000", 2) # scroll down
mouse.Controller().position = (x, y)
if buttons[0] and not left_pressed:
print("LEFT PRESSED")
mouse.Controller().press(mouse.Button.left)
left_pressed = 1
elif not buttons[0] and left_pressed:
print("LEFT RELEASED")
mouse.Controller().release(mouse.Button.left)
left_pressed = 0
if buttons[1] and not middle_pressed:
print("MIDDLE PRESSED")
mouse.Controller().press(mouse.Button.middle)
middle_pressed = 1
elif not buttons[1] and middle_pressed:
print("MIDDLE RELEASED")
mouse.Controller().release(mouse.Button.middle)
middle_pressed = 0
if buttons[2] and not right_pressed:
print("RIGHT PRESSED")
mouse.Controller().press(mouse.Button.right)
right_pressed = 1
elif not buttons[2] and right_pressed:
print("RIGHT RELEASED")
mouse.Controller().release(mouse.Button.right)
right_pressed = 0
if buttons[3]:
print("SCROLLUP PRESSED")
mouse.Controller().scroll(0, 2)
if buttons[4]:
print("SCROLLDOWN PRESSED")
mouse.Controller().scroll(0, -2)
#print("PointerEvent", buttonmask, x, y)
mousecontroller.process_event(sock.recv(5, socket.MSG_WAITALL))
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 +385,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))
@@ -629,19 +393,20 @@ class VncServer():
lastshot = rectangle
sendbuff = bytearray()
firstUpdateSent = False
self.encoding_object.firstUpdateSent = False
# try to send only the actual changes
if self.framebuffer != None and incremental == 1:
diff = ImageChops.difference(rectangle, self.framebuffer)
if diff.getbbox() is None:
# no changes...
rectangles = 0
sendbuff.extend(pack("!BxH", 0, rectangles))
try:
sock.sendall(sendbuff)
except:
return False
sleep(0.3)
sleep(0.1)
return
if diff.getbbox() is not None:
@@ -650,126 +415,77 @@ 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)
if self.bpp == 32 or self.bpp == 8:
if rectangle.mode is not "RGB":
image = rectangle.convert("RGB")
else:
image = rectangle
b = np.asarray(image)
a = b.copy()
del b
if self.bpp == 32 or self.bpp == 16 or self.bpp == 8:
if self.bpp == 32:
redBits = 8
greenBits = 8
blueBits = 8
elif self.bpp == 8:
redBits = 4
greenBits = 4
blueBits = 4
#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)
# image array
a = np.asarray(rectangle).copy()
if self.primaryOrder == "bgr": # bit shifting
blueMask = (1 << blueBits) - 1
greenMask = ((1 << greenBits) - 1) << self.green_shift
redMask = ((1 << redBits) - 1) << self.red_shift
if self.primaryOrder == "bgr":
self.blue_shift = 0
blueMask = (1 << blueBits) - 1
self.green_shift = blueBits
greenMask = ((1 << greenBits) - 1) << self.green_shift
self.red_shift = self.green_shift + greenBits
redMask = ((1 << redBits) - 1) << self.red_shift
a[..., 0] = ( a[..., 0] ) & blueMask >> self.blue_shift
a[..., 1] = ( a[..., 1] ) & greenMask >> self.green_shift
a[..., 2] = ( a[..., 2] ) & redMask >> self.red_shift
else: # RGB
redMask = ((1 << redBits) - 1) << self.red_shift
greenMask = ((1 << greenBits) - 1) << self.green_shift
blueMask = ((1 << blueBits) - 1) << self.blue_shift
a[..., 0] = ( a[..., 0] ) & redMask >> self.red_shift
a[..., 1] = ( a[..., 1] ) & greenMask >> self.green_shift
a[..., 2] = ( a[..., 2] ) & blueMask >> self.blue_shift
else: # RGB
self.red_shift = 0
redMask = (1 << redBits) - 1
self.green_shift = redBits
greenMask = ((1 << greenBits) - 1) << self.green_shift
self.blue_shift = self.green_shift + greenBits
blueMask = ((1 << blueBits) - 1) << self.blue_shift
a[..., 0] = ( a[..., 0] ) & redMask >> self.red_shift
a[..., 1] = ( a[..., 1] ) & greenMask >> self.green_shift
a[..., 2] = ( a[..., 2] ) & blueMask >> self.blue_shift
image = Image.fromarray(a)
del a
if self.primaryOrder == "rgb":
(b, g, r) = image.split()
image = Image.merge("RGB", (r, g, b))
del b,g,r
if self.bpp == 32:
image = Image.fromarray(a)
if self.primaryOrder == "rgb":
(b, g, r) = image.split()
image = Image.merge("RGB", (r, g, b))
del b,g,r
image = image.convert("RGBX")
elif self.bpp == 8:
#FIXME: improve 8 bit routines up!, image looks awful!
if rectangle.mode is not "RGB":
image = rectangle.convert("RGB")
else:
image = rectangle
if self.bpp == 16: #BGR565
greenBits = 5
blueBits = 6
redBits = 5
(r, g, b) = image.split()
image = Image.merge("RGB", (g, r, b))
del b,g,r
image = rectangle
if self.depth == 16:
image = image.convert('BGR;16')
if self.depth == 15:
image = image.convert('BGR;15')
elif self.bpp == 8: #bgr233
redBits = 3
greenBits = 3
blueBits = 2
image = rectangle
p = Image.new('P',(16,16))
p.putpalette(bgr233_palette.palette)
p = Image.new("P",(16,16))
p.putpalette(self.BGR233)
image = quantizetopalette(image, p, dither=self.CONFIG._8bitdither)
image.putpalette(self.BGR233)
del p
rectangles = 1
sendbuff.extend(pack("!BxH", 0, rectangles))
sendbuff.extend(pack("!HHHH", x, y, w, h))
sendbuff.extend(pack(">i", self.encoding))
sendbuff.extend( image.tobytes() )
#image = image.convert('RGB', colors=4).quantize(palette=p)
#log.debug(image)
if hasattr(enc.ENCODINGS, "zlib") and self.encoding == enc.ENCODINGS.zlib - 9998:
if not firstUpdateSent:
firstUpdateSent = True
compress = zlib.compressobj(
zlib.Z_DEFAULT_COMPRESSION, # level: 0-9
zlib.DEFLATED, # method: must be DEFLATED
-15, # window size in bits:
# -15..-8: negate, suppress header
# 8..15: normal
# 16..30: subtract 16, gzip header
1, # mem level: 1..8/9
zlib.Z_DEFAULT_STRATEGY # strategy:
# 0 = Z_DEFAULT_STRATEGY
# 1 = Z_FILTERED
# 2 = Z_HUFFMAN_ONLY
# 3 = Z_RLE
# 4 = Z_FIXED
)
rectangles = 1
sendbuff.extend(pack("!BxH", 0, rectangles))
sendbuff.extend(pack("!HHHH", x, y, w, h))
sendbuff.extend(pack(">i", self.encoding))
print("Compressing...")
zlibdata = compress.compress( image.tobytes() )
zlibdata += compress.flush()
l = pack("!I", len(zlibdata) )
sendbuff.extend( l ) # send length
sendbuff.extend( zlibdata ) # send compressed data
else:
# send with RAW encoding
sendbuff.extend(enc.ENCODINGS.raw_send_image(x, y, w, h, image))
# send image with client defined encoding
sendbuff.extend(self.encoding_object.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 +494,4 @@ class VncServer():
# connection closed?
return False
sock.settimeout(stimeout)
#print("end SendRectangles")
#log.debug("end SendRectangles")

View File

@@ -1,2 +1,5 @@
pyuserinput
pydes
pynput
numpy
Pillow-PIL
elevate

View File

@@ -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,21 @@ 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)
if sys.platform in ['win32', 'win64']:
from lib.oshelpers import windows as win32
if not win32.is_admin():
ret = win32.run_as_admin()
if ret is None:
log.debug("Respawning with admin rights")
sys.exit(0)
elif ret is True:
# admin rights
log.debug("Running with admin rights!")
else:
print('Error(ret=%d): cannot elevate privilege.' % (ret))
sys.exit(1)
while True:
sockServer.listen(4)
(conn, (ip,port)) = sockServer.accept()
@@ -95,8 +124,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()

16
test.py Normal file
View File

@@ -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")

6
test.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
for a in $(seq 1 5); do
echo "$a $$"
sleep 1
done

79
winservice.py Normal file
View File

@@ -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)