Raw Sockets : forger et sniffer des paquets en C
Nous avons parlé de sockets TCP en C
, sous le confort de SOCK_STREAM. Passons sous le kernel: les raw sockets, là où tcpdump et Nemesis vivent. Avertissement d’usage: SOCK_RAW demande root, la carte réseau en mode promiscuous et un peu d’éthique, le tout sans contrat avec personne.
Sniffer: AF_PACKET et le mode promiscuous
int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
Deux lignes et vous recevez tout ce qui traverse l’interface, en-têtes Ethernet comprises, à condition de passer l’interface en promiscuous:
struct ifreq ifr;
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
ioctl(s, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_PROMISC;
ioctl(s, SIOCSIFFLAGS, &ifr);
La curiosité, au niveau 2, ne se configure pas, elle se paie: le jour où le switch miroite votre port à votre insu, vous comprenez que la lecture des conversations d’autrui est un sport très surveillé. Le paquet brut arrive dans un buffer: Ethernet (14 octets), puis IP, puis TCP. La lecture manuelle, sans framework:
struct ethhdr *eth = (void *)buf;
struct iphdr *ip = (void *)(buf + 14);
struct tcphdr *tcp = (void *)(buf + 14 + ip->ihl * 4);
if (ntohs(eth->h_proto) == ETH_P_IP && ip->protocol == IPPROTO_TCP)
printf("%s:%u -> %s:%u\n",
inet_ntoa(*(struct in_addr *)&ip->saddr), ntohs(tcp->source),
inet_ntoa(*(struct in_addr *)&ip->daddr), ntohs(tcp->dest));
ip->ihl * 4: la longueur de l’en-tête IP est en mots de 32 bits et les options existent. L’oublier, c’est parser le payload en croyant parser TCP.
Forger: construire un SYN à la main
Forger un paquet, c’est remplir les champs, calculer les checksums, envoyer:
unsigned short checksum(unsigned short *p, int len) {
unsigned long sum = 0;
while (len > 1) { sum += *p++; len -= 2; }
if (len) sum += *(unsigned char *)p;
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16);
return ~sum;
}
Le checksum IP se calcule sur l’en-tête seul; le checksum TCP sur le pseudo-en-tête (IP src, IP dst, protocole, longueur TCP) plus le segment. Puis sendto() sur le raw socket avec IP_HDRINCL activé (setsockopt, sinon le kernel écrit l’en-tête IP à votre place et vous n’êtes pas au commande). Le tcphdr a des champs bitfield (syn:1, ack:1…), l’ordre des bits dépend de l’endianness et la moitié des bugs de forage viennent de là.
Un SYN forcé sans handshake, c’est un port scan: SYN sur 1-1024, recvfrom() les réponses, SYN/ACK = ouvert, RST = fermé, silence = filtré. C’est ce que fait nmap et ce que faisaient Nemesis et Yersinia, en plus poli.
Ce que ça vaut
Le raw socket, c’est le niveau de compréhension où l’on ne parle plus de “ports” mais de flags, de séquences et de checksums. On y apprend que tcpdump ne fait pas de magie, il recvfrom(); que Nemesis ne forge pas mieux que vous, il a juste fini ses bitfields; et que root + promiscuous sur un switch, c’est surtout un mirroring qui attend d’être configuré. La suite: un serveur qui accepte dix mille connexions sans fork mille fois. epoll m’attend.