miércoles, mayo 12, 2010

Acceso directo al driver de red

Estoy con mi proyecto de fin de carrera en el que pretendo desarrollar un inyector de paquetes (sí otro más), así que investigo bastante en este tema. Esta entrada es para aquella gente que sabe de lo que hablo porque voy a sobreentender que se saben ya bastantes cosas de redes y de programación con sockets.

He estado investigando cómo conseguir llegar a inyectar lo que yo quiera en el cable de red, y con esto me refiero a que por el cable vaya una ristra de bytes tal cual yo la he definido, sin más cabeceras ni información añadida por ningún driver, ni SO y lo he logrado.

Como muchos saben en sistemas POSIX existe o podría existir la llamada al sistema socket(domain, type, protocol), que permite iniciar un socket para la comunicación por red. Siempre la he mirado con mucho recelo porque aunque me permite una gran flexibilidad, lo máximo que yo sabía de a dónde podía llegar es a actuar por encima del nivel de red, concretamente por encima de IP mediante SOCK_RAW.

Hoy he descubierto, después de mirar mucho código, de leer mucho man y buscar en internet, que se puede llegar mas lejos (como suponía) y he logrado acceder totalmente a la red.

En el man socket se especifica el domain AF_PACKET para acceder al bajo nivel, también se comenta consultar packet(7). Ese es el dominio que hace falta para acceder a bajo nivel, en el siguiente man nos comentan que existe un type SOCK_RAW que nos permite acceder al driver ¡sin que este añada cabeceras de ningún tipo!. Pero no todo está hecho, hay que aprender a usar todo esto. El siguiente codigo es un ejemplo:

#include
#include
#include
#include
#include

#include
#include
#include

#include
#include
#include

int main(int argc, char *argv[]) {
char *buf = "Hola\0";
int fd, c;
struct ifreq ifr;
struct sockaddr_ll sa;

fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (fd < 0) {
fprintf(stderr, "socket fail\n");
exit(-1);
}

memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, "eth0", strlen("eth0"));
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
fprintf(stderr, "ioctl fail\n");
exit(-1);
}

sa.sll_family = AF_PACKET;
sa.sll_ifindex = ifr.ifr_ifindex;
sa.sll_protocol = htons(ETH_P_ALL);
c = sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&sa, sizeof(sa));
if (c < 0) {
fprintf (stderr, "write fail\n");
exit(-1);
}

exit(0);
}

Primero hay que abrir el socket con las características especificadas, luego nos tenemos que enterar mediante el uso de ioctl cual es el index de la interfaz que queremos usar (eth0 en este caso). Ese número es el identificador de interfaces que ha reconocido el SO, piensen que al abrir el socket no se le especifica en ningún lado en que tarjeta debería escribir y leer, de hecho es independiente del socket. Eso se especifica en cada lectura y cada escritura como vemos en la llamada a sendto. Se ha preparado una estructura struct sockaddr_ll (ya conocen los struct sockaddr, este es el _ll link layer ;), en el campo sll_ifindex que averiguamos previamente con ioctl.

Evidentemente sobra decir que para que ese código funcione hay que ejecutarlo con permisos de root o, como documenta el manual, con CAP_NET_RAW.

Pues eso, así es como se inyecta directamente y sin pasar por bibliotecas como libnet, sino yendo directamente a los servicios del SO.

Happy hacking!