# TCP/IP raw sockets en C sur GNU/Linux

description : Création et utilisation des TCP/IP raw sockets en C sur GNU/Linux
categories : Coding; Networking;
tags : C; Linux;

Les raw sockets sur GNU/Linux pour les protocoles TCP/IP en C

Qu'est ce qu'une "socket" ?

Une socket est un endpoint, soit un point de terminaison permettant la communication entre deux processus.

Sur un réseau, une socket, c'est donc un endpoint pour envoyer ou recevoir des données permettant ainsi des interactions entre le client et le serveur.

Le serveur étant le poste informatique qui fournit le service, comme par exemple le service http qui se trouve par convention sur le port 80, et le client étant le poste qui va consommer ou utiliser le service.

Pour pouvoir communiquer entre eux, une socket est créée sur le serveur, celle ci est configurer de façon à écouter sur un port, donc à attendre les connexions sur ce port.

Sur le client, la socket est utilisée pour se connecter au port du service. La connexion se fait en configurant la socket avec l'IP ou le nom de domaine du serveur ainsi que le port du service sur lequel se connecter.

Ensuite l'échange, le dialogue ou la communication entre le client et le serveur sur le réseau se fait par paquets au moyen d'une suite de protocoles.

Qu'est ce qu'un "protocole" ?

En informatique, un protocole est un ensemble de règles standard permettant de communiquer.

Internet qui a été créé suite à un projet de recherche sur les interconnexions de réseaux mené par la DARPA (Defense Advanced Research Projects Agency) dépendant du DoD (Department of Defense) Américain repose en partie sur les protocoles TCP/IP et UDP/IP.

Qu'est ce que le "protocole IP"

IP pour Internet Protocol est le protocole de routage se chargeant donc de l'acheminement des paquets.

Le routage c'est le moyen avec l'aide d'algorithme de trouver pour la source la meilleure route pour accéder à la destination.

Pour plus d'information sur le protocole IP, vous pouvez consulter la RFC 791

Dans un paquet les informations nécessaires pour le protocole IP sont définit dans l'en-tête IP, soit le IP header. Voici son format pour la version 4 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|      Fragment Offset    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |         Header Checksum       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Pour de la programmation en C et sur Linux, nous trouvons la définition de l'header IP version 4 dans le fichier /usr/include/netinet/ip.h. Voici la structure :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct iphdr
{
  #if __BYTE_ORDER == __LITTLE_ENDIAN
  unsigned int ihl:4;
  unsigned int version:4;
  #elif __BYTE_ORDER == __BIG_ENDIAN
  unsigned int version:4;
  unsigned int ihl:4;
  #else
  # error	"Please fix <bits/endian.h>"
  #endif
  uint8_t tos;
  uint16_t tot_len;
  uint16_t id;
  uint16_t frag_off;
  uint8_t ttl;
  uint8_t protocol;
  uint16_t check;
  uint32_t saddr;
  uint32_t daddr;
  /* The options start here. */
};

Qu'est ce que le "protocole TCP"

TCP pour Transmission Control Protocol fournit tout en s'appuyant sur le protocole IP un service de remise des paquets de façon contrôlé, donc fiable.

TCP se charge entre autre de l'établissement et de la libération d'une connexion, du transfert de données, de la corrections d'erreurs et du contrôle de ses données par un système d'accusé de reception, un système d'acknowledgement / d'acquittement (ACK).

La RFC concernant le protocole TCP se trouve ici : https://www.rfc-editor.org/rfc/rfc793.html

L'header TCP suit le format suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Data |           |U|A|P|R|S|F|                               |
| Offset| Reserved  |R|C|S|S|Y|I|            Window             |
|       |           |G|K|H|T|N|N|                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum            |         Urgent Pointer        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             data                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Sur Linux pour de la programmation en langage C, nous pouvons trouver la structure représentant l'header TCP dans le fichier /usr/include/netinet/tcp.h :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
 * TCP header.
 * Per RFC 793, September, 1981.
 */
struct tcphdr
{
  __extension__ union
  {
    struct
    {
      uint16_t th_sport;	/* source port */
      uint16_t th_dport;	/* destination port */
      tcp_seq th_seq;		/* sequence number */
      tcp_seq th_ack;		/* acknowledgement number */
      # if __BYTE_ORDER == __LITTLE_ENDIAN
      uint8_t th_x2:4;	/* (unused) */
      uint8_t th_off:4;	/* data offset */
      # endif
      # if __BYTE_ORDER == __BIG_ENDIAN
      uint8_t th_off:4;	/* data offset */
      uint8_t th_x2:4;	/* (unused) */
      # endif
      uint8_t th_flags;
      # define TH_FIN	0x01
      # define TH_SYN	0x02
      # define TH_RST	0x04
      # define TH_PUSH	0x08
      # define TH_ACK	0x10
      # define TH_URG	0x20
      uint16_t th_win;	/* window */
      uint16_t th_sum;	/* checksum */
      uint16_t th_urp;	/* urgent pointer */
    };
    struct
    {
      uint16_t source;
      uint16_t dest;
      uint32_t seq;
      uint32_t ack_seq;
      # if __BYTE_ORDER == __LITTLE_ENDIAN
      uint16_t res1:4;
      uint16_t doff:4;
      uint16_t fin:1;
      uint16_t syn:1;
      uint16_t rst:1;
      uint16_t psh:1;
      uint16_t ack:1;
      uint16_t urg:1;
      uint16_t res2:2;
      # elif __BYTE_ORDER == __BIG_ENDIAN
      uint16_t doff:4;
      uint16_t res1:4;
      uint16_t res2:2;
      uint16_t urg:1;
      uint16_t ack:1;
      uint16_t psh:1;
      uint16_t rst:1;
      uint16_t syn:1;
      uint16_t fin:1;
      # else
      #  error "Adjust your <bits/endian.h> defines"
      # endif
      uint16_t window;
      uint16_t check;
      uint16_t urg_ptr;
    };
  };
};

Qu'est ce que le "protocole UDP"

UDP qui signifie User Datagram Protocol et qui s'appuie également sur le protocole IP permet la remise de paquet de façon non contrôlé et donc non fiable.

Par exemple, il ne garantit ni la remise, ni l'ordre des paquets délivrés, et il ne pocède pas à de contrôle sur les données échangées.

Vous pouvez consulter la RFC 768 ici : https://www.rfc-editor.org/rfc/rfc768.html

Voici le format de l'header UDP :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
|     Source      |   Destination   |
|      Port       |      Port       |
+--------+--------+--------+--------+
|                 |                 |
|     Length      |    Checksum     |
+--------+--------+--------+--------+
|
|          data octets ...
+---------------- ...

Nous trouvons la déclaration de cette structure dans le fichier /usr/include/netinet/udp.h :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* UDP header as specified by RFC 768, August 1980. */

struct udphdr
{
  __extension__ union
  {
    struct
    {
      uint16_t uh_sport;	/* source port */
      uint16_t uh_dport;	/* destination port */
      uint16_t uh_ulen;		/* udp length */
      uint16_t uh_sum;		/* udp checksum */
    };
    struct
    {
      uint16_t source;
      uint16_t dest;
      uint16_t len;
      uint16_t check;
    };
  };
};

Comment forger un paquet TCP ?

En TCP une connexion s'effectue en envoyant une requête de type SYN.

Nous allons voir comment forger un paquet TCP contenant la requête SYN.

Pour le checksum, nous pouvons trouver la fonction provenant de la RFC 1071 : https://www.rfc-editor.org/rfc/rfc1071.html permettant de le calculer.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/in.h>

#include <arpa/inet.h>

struct pseudohdr
{
  int32_t src;
  int32_t dst;
  uint8_t pad;
  uint8_t proto;
  uint16_t tcp_len;
  struct tcphdr tcp;
};

uint16_t checksum(uint16_t *addr, int32_t len);
uint16_t tcp_checksum(int32_t src, int32_t dst, uint16_t *addr, int32_t len);

int main(int argc, char *argv[])
{
  int32_t sd;
  const int on = 1;
  struct iphdr ip;
  struct tcphdr tcp;
  struct sockaddr_in sin;
  uint8_t *packet;
  uint32_t saddr;
  uint16_t source;
  uint32_t daddr;
  uint16_t dest;

  if (argc != 5)
  {
    fprintf(stderr, "USAGE: %s <ip src> <port src> <ip dest> <port dest>\n", argv[0]);
    exit(1);
  }

  source = strtol(argv[2], NULL, 10);
  dest = strtol(argv[4], NULL, 10);

  if (!inet_pton(AF_INET, argv[1], &saddr))
  {
    perror("inet_pton()");
  }

  if (!inet_pton(AF_INET, argv[3], &daddr))
  {
    perror("inet_pton()");
  }

  if ((source == LONG_MIN) || (source == LONG_MAX))
  {
    perror("strtol()");
  }

  if ((dest == LONG_MIN) || (dest == LONG_MAX))
  {
    perror("strtol()");
  }

  packet = (uint8_t *)malloc(sizeof(struct iphdr) + sizeof(struct tcphdr));

  if ((sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
    perror("socket()");
    exit(1);
  }

  if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
    perror("setsockopt");
    exit(1);
  }

  ip.ihl = 5;
  ip.version = 4;
  ip.tos = 0;
  ip.tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr));
  ip.id = 0;
  ip.frag_off = 0;
  ip.ttl = 128;
  ip.protocol = IPPROTO_TCP;
  ip.check = 0;
  ip.saddr = saddr;
  ip.daddr = daddr;

  tcp.source = htons(source);
  tcp.dest = htons(dest);
  tcp.seq = 0;
  tcp.ack_seq = 0;
  tcp.res1 = 0;
  tcp.doff = sizeof(struct tcphdr) / 4;
  tcp.fin = 0;
  tcp.syn = 1; //
  tcp.rst = 0;
  tcp.psh = 0;
  tcp.ack = 0;
  tcp.urg = 0;
  tcp.res2 = 0;
  tcp.window = htons(65535);
  tcp.check = 0;
  tcp.urg_ptr = 0;

  ip.check = checksum((uint16_t *)&ip, sizeof(struct iphdr));
  tcp.check = tcp_checksum(saddr, daddr, (uint16_t *)&tcp, sizeof(struct tcphdr));

  memcpy(packet, &ip, sizeof(struct iphdr) * sizeof(uint8_t));
  memcpy((packet + sizeof(struct iphdr)), &tcp, sizeof(struct tcphdr) * sizeof(uint8_t));

  memset(&sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = inet_addr(argv[3]);

  if (sendto(sd, packet, sizeof(struct iphdr) + sizeof(struct tcphdr), 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0)  {
    perror("sendto()");
    exit(1);
  }

  return 0;
}

uint16_t checksum(uint16_t *addr, int32_t len)
{
  int32_t count = len;
  register uint32_t sum = 0;
  uint16_t *w = addr;
  uint16_t answer = 0;

  while (count > 1) {
    sum += *w++;
    count -= 2;
  }

  if (count == 1) {
    *(uint8_t *)(&answer) = *(uint8_t *)w;
    sum += answer;
  }

  sum = (sum >> 16) + (sum & 0xFFFF);
  sum += (sum >> 16);
  answer = ~sum;

  return answer;
}

uint16_t tcp_checksum(int32_t src, int32_t dst, uint16_t *addr, int32_t len)
{
  struct pseudohdr pseudo;

  memset(&pseudo, 0, sizeof(struct pseudohdr));

  pseudo.src = src;
  pseudo.dst = dst;
  pseudo.pad = 0;
  pseudo.proto = IPPROTO_TCP;
  pseudo.tcp_len = htons(len);

  memcpy(&(pseudo.tcp), addr, len);

  return checksum((uint16_t *)&pseudo, sizeof(struct pseudohdr));
}

Je compile :

$ gcc tcp-raw-socket.c -o tcp-raw-socket -Wall

A cette étape je décide de créer une VM. Cette VM a pour IP 192.168.1.29. Sur cette VM je vais écouter sur le port 4444 et l'interface enp0s3 avec tcpdump : https://www.tcpdump.org/

Donc, si j'exécute tcpdump sur le port 3333 de mon système hôte, que j'éxécute tcp-raw-socket sur ce système et que je tcpdump sur le port 4444 de la cible, voici l'output de tcpdump du système hôte, donc du système qui envoit le paquet :

$ sudo ./tcp-raw-socket 192.168.1.192 3333 192.168.1.29 4444
1
2
3
4
5
6
$ sudo tcpdump -vv -n -i wlp2s0 'port 3333'
tcpdump: listening on wlp2s0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:36:01.692070 IP (tos 0x0, ttl 128, id 9175, offset 0, flags [none], proto TCP (6), length 40)
    192.168.1.192.3333 > 192.168.1.29.4444: Flags [S], cksum 0x0d54 (correct), seq 0, win 8192, length 0
16:36:01.692665 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    192.168.1.29.4444 > 192.168.1.192.3333: Flags [R.], cksum 0x0d41 (correct), seq 0, ack 1, win 0, length 0

L'output du tcpdump sur la machine 192.168.1.29 :

1
2
3
4
5
6
$ sudo tcpdump -i enp0s3 -vv -n 'port 4444'
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
16:36:01.739188 IP (tos 0x0, ttl 128, id 9175, offset 0, flags [none], proto TCP (6), length 40)
    192.168.1.192.3333 > 192.168.1.29.4444: Flags [S], cksum 0x0d54 (correct), seq 0, win 8192, length 0
16:36:01.739261 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    192.168.1.29.4444 > 192.168.1.192.3333: Flags [R.], cksum 0x0d41 (correct), seq 0, ack 1, win 0, length 0

IP Spoofing

IP Spoofing Appliqué par truff@projet7.org (01/2002) : http://www.ouah.org/ipspapp.htm

D'abord, j'ouvre le port 4444 sur la VM 192.168.1.29 et je sniff ce port avec tcpdump.

$ netcat -l -p 4444

Ensuite, je lance un autre VM qui a pour IP 192.168.1.36 sur la quelle je sniff la connexion avec tcpdump sur le port 4444.

Dernière chose, avec tcp-raw-socket, je change l'adresse IP source 192.168.1.192 par l'IP de la VM 192.168.1.36 :

$ sudo ./tcp-raw-socket 192.168.1.36 3333 192.168.1.29 4444

Voici l'output de tcpdump sur la machine hôte 192.168.1.192, donc la machine qui envoit :

1
2
3
4
$ sudo tcpdump -vv -n -i wlp2s0 'port 3333'
tcpdump: listening on wlp2s0, link-type EN10MB (Ethernet), capture size 262144 bytes
13:26:22.727599 IP (tos 0x0, ttl 128, id 60809, offset 0, flags [none], proto TCP (6), length 40)
    192.168.1.36.3333 > 192.168.1.29.4444: Flags [S], cksum 0x0df0 (correct), seq 0, win 65535, length 0

L'output tcpdump pour la VM 192.168.1.29, celle qui recoit :

1
2
3
4
5
6
7
8
$ sudo tcpdump -i enp0s3 -vv -n 'port 4444'
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
13:26:22.727537 IP (tos 0x0, ttl 128, id 60809, offset 0, flags [none], proto TCP (6), length 40)
    192.168.1.36.3333 > 192.168.1.29.4444: Flags [S], cksum 0x0df0 (correct), seq 0, win 65535, length 0
13:26:22.728366 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 44)
    192.168.1.29.4444 > 192.168.1.36.3333: Flags [S.], cksum 0x83b0 (incorrect -> 0xb1ec), seq 2121159607, ack 1, win 29200, options [mss 1460], length 0
13:26:22.728869 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    192.168.1.36.3333 > 192.168.1.29.4444: Flags [R], cksum 0x0ded (correct), seq 1, win 0, length 0

Et pour la VM dont l'ip a été spoofé :

1
2
3
4
5
6
$ sudo tcpdump -n -vv -i enp0s3 'port 4444'
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
13:26:22.728907 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 44)
    192.168.1.29.4444 > 192.168.1.36.3333: Flags [S.], cksum 0xb1ec (correct), seq 2121159607, ack 1, win 29200, options [mss 1460], length 0
13:26:22.728968 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
    192.168.1.36.3333 > 192.168.1.29.4444: Flags [R], cksum 0x0ded (correct), seq 1, win 0, length 0