Raw Sockets: forging and sniffing packets in C
We spoke of TCP sockets in C
, under the comfort of SOCK_STREAM. Let’s go below the kernel: raw sockets, where tcpdump and Nemesis live. Usual warning: SOCK_RAW requires root, the network card in promiscuous mode and some ethics, all without a contract with anyone.
Sniffing: AF_PACKET and promiscuous mode
int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
Two lines and you receive everything crossing the interface, Ethernet headers included, provided the interface goes promiscuous:
struct ifreq ifr;
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
ioctl(s, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_PROMISC;
ioctl(s, SIOCSIFFLAGS, &ifr);
Curiosity, at layer 2, is not configured, it is paid for: the day the switch mirrors your port without telling you, you understand that reading other people’s conversations is a closely watched sport. The raw packet lands in a buffer: Ethernet (14 bytes), then IP, then TCP. Manual reading, no 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: the IP header length is counted in 32-bit words and options exist. Forgetting it means parsing the payload while believing you parse TCP.
Forging: building a SYN by hand
Forging a packet means filling fields, computing checksums, sending:
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;
}
The IP checksum runs on the header alone; the TCP checksum runs on the pseudo-header (src IP, dst IP, protocol, TCP length) plus the segment. Then sendto() on the raw socket with IP_HDRINCL enabled (setsockopt, otherwise the kernel writes the IP header for you and you are not in command). The tcphdr has bitfields (syn:1, ack:1…), bit order depends on endianness and half of all forging bugs come from there.
A forced SYN without a handshake is a port scan: SYN over 1-1024, recvfrom() the replies, SYN/ACK = open, RST = closed, silence = filtered. That is what nmap does and what Nemesis and Yersinia did, more politely.
What it is worth
The raw socket is the level of understanding where one no longer speaks of “ports” but of flags, sequences and checksums. You learn that tcpdump does no magic, it recvfrom()s; that Nemesis does not forge better than you, it just finished its bitfields; and that root + promiscuous on a switch is mostly a mirroring waiting to be configured. Next: a server that accepts ten thousand connections without forking ten thousand times. epoll awaits.