Challenges de programmation

Venez résoudre différents problèmes de programmation en C !

Le problème exposé dans ce sujet a été résolu.

Bon je me [re]lance

Challenge 2

Backslash à la fin de la ligne du commentaire "Attention si problème…" => la ligne suivante est unifiée par le préprocesseur à cette ligne, donc est en commentaire

"Problème d'allocation mémoire" est donc systématiquement affiché

Challenge 3

En entrant plus de 257 caractères dans l'argument passé au programme, on va écrire dans la variable top_secret (qui est contigue en mémoire à buffer) une valeur différente de 0 lorsque cet argument est copié dans le buffer donc on entre dans la partie secrète

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ ./a.exe ADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAA
Vous avez entre :
ADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAADAAA

Vous etes rentre dans la partie top secrete du programme !

+1 -0

Bon allez, je vous donne le code du challenge 4 version non-obfusqué :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>

char *s_ = "wzqzmv{*##bv/{}qzv=*|v|:;r/az~v=*|ve*&#;r/m|#vbm*|#evb#evez@zm~v=*|;r/tbnzv=*|v$m=;r/@b=v{**e`=z;r/~zaavbva}zvb#ev'|m~v=*|lr/";

char *tr_ = "r~{nb@:#wmq;*=&z`'$}t|vlea\ntgkaspnNrv,oywebhcimu !dl";

char *main(int index, char *s, char *tr, int r, int t)
{
   if(index == 7)
      return 0;
   if(index == 1 && r != 1)
      main(index, s_, tr_, 1, t);
   if(r > 1) {
      if(t >= 3) {
         if(*s == '/')
            return "";
         main(index, s, tr, r, -9);
         main(index, s + 1, tr, r, 3);
      }
      else if(t < 0) {
         if(*s == *tr)
            putchar(tr[26]);
         else
            main(index, s, tr + 1, r, -7);
      }
      else {
         if(index == 0)
            return s;
         if(*s == '/')
            return main(index + 1, s + 1, tr, r, 2);
         else
            return main(index, s + 1, tr, r, 1);
      }
   }

   if(r == 1) {
      main(0, main(0, s, "", 2, 1), tr, 2, 12);
      main(-index, main(-index, s, "", 2, 2), tr, 2, 27);
      main(++index, s, tr, r, t);
   }
}

Challenge 4 - Version désobfusquée bis - J'avais fait le travail inital donné par napnac mais comme le résultat est très similaire je ne le remets pas ici.

En isolant l'appel initial dans un main standard, et en simplifiant au maximum tout en conservant une seule fonction récursive. Je compile avec des options strictes donc je suis obligé de mettre des return partout.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>

const char *recur(int, const char *, const char *, int, int);

int main(void) {
  recur(1, "wzqzmv{*##bv/{}qzv=*|v|:;r/az~v=*|ve*&#;r/m|#vbm*|#evb#evez@zm~v=*|;r/tbnzv=*|v$m=;r/@b=v{**e`=z;r/~zaavbva}zvb#ev'|m~v=*|lr/", "r~{nb@:#wmq;*=&z`'$}t|vlea\ntgkaspnNrv,oywebhcimu !dl", 1, 0);
  return EXIT_SUCCESS;
}

const char *recur(int l, const char *s, const char *_, int r, int t) {
  if (l == 7) {
      return NULL;
  }
  if (r == 2) {
      if (t > 0) {
          if (*s == '/') {
              return NULL;
          }
          if (recur(l, s, _, r, -1)) {
              return recur(l, ++s, _, r, 1);
          }
          return NULL;
      }
      else if (t < 0) {
          if (*s == *_) {
              putchar(*(_+26));
              return _+26;
          }
          else {
              return recur(l, s, ++_, r, -1);
          }
      }
      else {
          if (!l) {
              return s;
          }
          if (*s == '/') {
              return recur(++l, ++s, _, r, 0);
          }
          return recur(l, ++s, _, r, 0);
      }
  }
  else if (r == 1) {
      recur(0, s, _, 2, 1);
      recur(-l, recur(-l, s, _, 2, 0), _, 2, 1);
      return recur(++l, s, _, r, t);
  }
  else {
      return NULL;
  }
}

Quelques constatations de base:

  • Il y un premier niveau de récursivité (r = 1) qui en appelle un 2ème (r = 2)

  • Pour r = 2, il y a 3 types de traitement (t > 0 (t1), t < 0 (t2), t = 0 (t3)) dont on peut tirer 3 fonctions, le 1er niveau de récursivité peut également constituer une fonction appelant les 3 autres

  • t1 appelle t2 et lui-même

  • t2 et t3 s'appellent eux-même

EDIT: traduction en version itérative.

Les traitements s'appelant eux-même peuvent être transformés en boucle (j'ai rajouté des tests sur les fins de chaine par sécurité)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void t0(const char *, const char *);
void t1(const char *, const char *);
const char *t3(int, const char *);

int main(void) {
  t0("wzqzmv{*##bv/{}qzv=*|v|:;r/az~v=*|ve*&#;r/m|#vbm*|#evb#evez@zm~v=*|;r/tbnzv=*|v$m=;r/@b=v{**e`=z;r/~zaavbva}zvb#ev'|m~v=*|lr/", "r~{nb@:#wmq;*=&z`'$}t|vlea\ntgkaspnNrv,oywebhcimu !dl");
  return EXIT_SUCCESS;
}

void t0(const char *s, const char *_) {
int n;
  for (n = 1; n < 7; n++) {
      t1(s, _);
      t1(t3(n, s), _);
  }
}

void t1(const char *s, const char *_) {
  while (*s && *s != '/') {
      char *f = strchr(_, *s); /* t2 */
      if (f) {
          putchar(f[26]);
      }
      else {
          break;
      }
      s++;
  }
}

const char *t3(int n, const char *s) {
  while (n > 0 && *s) {
      if (*s == '/') {
          n--;
      }
      s++;
  }
  return s;
}

Ensuite en donnant des noms de fonctions/variables plus parlant on voit mieux de quoi il retourne.

J'ai rajouté en tant que paramètre de fonction tout ce qui pouvait l'être, et également modifié la boucle initiale ce qui permet de simplifier le programme.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void decrypt_text(const char *, int, const char *, int);
void decrypt_row(const char *, int, const char *, int);

int main(void) {
  decrypt_text("wzqzmv{*##bv/{}qzv=*|v|:;r/az~v=*|ve*&#;r/m|#vbm*|#evb#evez@zm~v=*|;r/tbnzv=*|v$m=;r/@b=v{**e`=z;r/~zaavbva}zvb#ev'|m~v=*|lr", '/', "r~{nb@:#wmq;*=&z`'$}t|vlea\ntgkaspnNrv,oywebhcimu !dl", 26);
  return EXIT_SUCCESS;
}

void decrypt_text(const char *text, int delimiter, const char *key, int offset) {
const char *row = strchr(text, delimiter);
  while (row) {
      decrypt_row(text, delimiter, key, offset);
      decrypt_row(row+1, delimiter, key, offset);
      row = strchr(row+1, delimiter);
  }
}

void decrypt_row(const char *row, int delimiter, const char *key, int offset) {
  while (*row && *row != delimiter) {
      char *found = strchr(key, *row);
      if (found) {
          putchar(found[offset]);
      }
      else {
          break;
      }
      row++;
  }
}

+1 -0

C'est beaucoup moins drôle du coup, je n'ai pas pris le temps de la chercher…

Saroupille

Le défi est en ligne depuis bientôt une semaine, et puis j'ai tout de même annoncé dans mon message le contenu de la balise secrète, donc si tu voulais réellement le faire par toi même il suffisait de ne pas ouvrir la balise. ;)

Oui, enfin, non obfusqué ne signifie pas plus compréhensible. Vous auriez pu arriver à la même chose simplement en rajoutant des espaces et des sauts de ligne dans la version d’origine. Ce que fait véritablement le code reste largement abstrus.

Dominus Carnufex

Exactement, mais je me suis dit que ça encouragerait justement ceux qui ont un la flemme de faire cette partie qui peut être fastidieuse.

Beau travail fromvega ! Mais du coup tu m'as juste fournit des codes plus présentables, j'attends encore au moins une courte explication sur la manière dont la sortie est générée à partir des deux chaînes (le mécanisme derrière, même si je pense que tu l'as compris). :p

j'attends encore au moins une courte explication sur la manière dont la sortie est générée à partir des deux chaînes (le mécanisme derrière, même si je pense que tu l'as compris). :p

napnac

okidoki - Challenge 4 - explication

Le but du programme est de décrypter une chaîne de caractère à l'aide d'une clé (qui est une autre chaîne).

La chaîne à décrypter est composée de N+1 tronçons séparés par des slashs ('/') qu'on notera T(0) à T(N).

Le programme affiche:

T(0) décrypté + T(1) décrypté

T(0) décrypté + T(2) décrypté

T(0) décrypté + T(N) décrypté

Pour chaque tronçon le décryptage fonctionne de la manière suivante:

  • On recherche chaque caractère du tronçon à décrypter dans la clé

  • Si le caractère est trouvé en position P dans la clé, le caractère affiché sera celui se trouvant dans la clé en position P+26

  • Le décryptage s'interrompt au 1er caractère non trouvé ou dès qu'un slash est rencontré (marquant la fin du tronçon)

Je profite de la "remontée" du sujet pour offrir une sorte de correction sur les différents exercices (car j'étais occupé pendant un certain temps, puis j'ai totalement oublié de le faire).

Exercice 1

Pas de solution à proprement parler pour cet exercice, et si vous souhaitez voir un exemple de code je vous redirige vers celui de fromvega : 78 caractères.

Exercice 2

Le premier "piège" relativement facile à trouver est le ; après le else, ce qui signifie que ce bloc de condition sera interprété comme un bloc vide, et donc que le printf sera exécuté quel que soit le résultat du deuxième if (car même si l'indentation induit en erreur, le printf n'appartient en réalité à aucun bloc de condition, et il sera donc toujours exécuté).

Le deuxième piège, plus sournois cette fois, concerne le deuxième if. En effet, on fait appel à une macro pour réaliser la multiplication, mais il y a un effet de bord à cause du + 1 et du + 2. Si on remplace la macro par son contenu, on obtient en réalité a + 1 * b + 2, ce qui n'est pas le résultat attendu puisque l'opérateur * a une priorité supérieure à l'opérateur +. Le résultat est alors 2 + 1 * 2 + 2 soit 6. Pour éviter toutes erreurs de ce type, il faut mettre des parenthèses autour des variables utilisées dans la macro, et aussi autour de l'expression comme ceci : #define multiplication(a, b) ((a) * (b)).

Le troisième piège est le plus difficile à trouver. La principale raison pour laquelle j'ai posté une photo au lieu du code directement n'est pas pour éviter tout copier/coller de votre part, mais surtout pour ne pas laisser la coloration syntaxique révéler l'astuce. Si on utilise un bloc de code coloré dans le bon langage, on obtient sur le premier if :

1
2
3
// Attention si probleme d'allocation ou de multiplication /!\
if(!s)
     printf("Probleme d'allocation memoire !\n");

Comme on peut le voir, le if est en réalité commenté à cause du dernier caractère sur le commentaire : \. Le backslash permet de créer un commentaire multiligne, tout comme il est possible de déclarer des macros sur plusieurs lignes à l'aide de la même méthode :

1
2
3
#define NOMBRE 1, \
               2, \
               3

Notre bloc de condition if est alors commenté, et le printf est donc forcément exécuté.

Au final, le programme affiche les trois sorties proposées, soit :

1
2
3
Probleme d'allocation memoire !
Probleme de multiplication !
Tout va bien !

Exercice 3

Il y a plusieurs façons d'exploiter le code proposé, et je vais vous indiquer celles que j'avais en tête lors de l'écriture de l'exercice :

  • buffer overflow sur notre variable buffer, ce qui permet de réécrire sur la partie mémoire de la variable top_secret vérifiant alors la condition et nous donnant donc l'accès à la partie secrète du programme. Plus d'informations dans la liste d'articles proposés (surtout celui concernant les buffer overflow), ou encore sur la réponse de fromvega à cet exercice.
  • exécution de code shell : il y a deux possibilités principalement pour exécuter du code malveillant dans ce programme. La première vient du buffer overflow vu dans le dernier point, et il est tout à fait possible d'injecter du code pour par exemple prendre le contrôle de la machine (exemple en vidéo : Buffer Overflow Attack - Computerphile ). La deuxième possibilité vient de la ligne printf(buffer); que l'on peut exploiter en utilisant les paramètres de format de printf afin d'injecter du code (exemple : How can a Format-String vulnerability be exploited?). Encore une fois, n'hésitez pas à lire l'article proposé dans l'exercice à propos des shellcode pour en apprendre davantage.
  • enfin il y avait aussi la possibilité d'exploiter un débordement de base dans le tas à cause du strcpy(buffer, argv[1]); (exemple d'utilisation : Débordement de tampon - dans le tas). L'article proposé à la fin de l'exercice sur le stack-based overflow est assez proche dans l'idée de l'exploitation (mais agit sur la pile), et il peut être intéressant de le consulter aussi.
Exercice 4

fromvega a déjà donné un code en clair, ainsi que plusieurs explications. Cependant, j'avais rapidement rédigé quelques étapes sur la conception du programme depuis zéro. Je vous laisse lire la partie concernée, n'hésitez pas à me poser des questions si les quelques explications ne sont pas claires, mais je vous invite tout de même à lire les messages de fromvega sur l'exercice.

J'espère que l'atelier vous aura plu ! :)

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte