Convertir une chaine en valeur hex en C
Dans le cadre de la résolution du challenge Go go gadgeto du CTF NoBrackets2025, je suis tombé sur une fonction qui convertit une entrée utilisateur en valeur hexadécimale :
int populate_chain(int size, gadget *chain) { int i, j; long addr; char buffer; for (i = 0; i < size; i++) { printf("Utilise ton prochain gadget inspecteur (format deadbeef1337babe) : "); fflush(stdout); addr = 0; for (j = 0; j < 16; j++) { buffer = getchar(); if (buffer > 47 && buffer < 58) { buffer -= 48; } else if (buffer > 96 && buffer < 103) { buffer -= 87; } else { return 0; } addr += pow(16, 15-j) * buffer; } if (getchar() != '\n') { return 0; } chain[i] = addr; } return 1; }
Le début de la fonction est plutot clair : une boucle for qui englobe tout le reste. Le reste du code est donc éxécuté size fois.
A chaque execution le programme imprime une chaine :
printf("Utilise ton prochain gadget inspecteur (format deadbeef1337babe) : ");
Ensuite il répète 16 fois (à l'aide d'une autre boucle for) :
buffer = getchar(); if (buffer > 47 && buffer < 58) { buffer -= 48; } else if (buffer > 96 && buffer < 103) { buffer -= 87; } else { return 0; } addr += pow(16, 15-j) * buffer;
Il récupère un caractere depuis l'entrée standard avec getchar qu'il stocke dans buffer, de type char :
char buffer;
Il compare ensuite sa valeur avec un entier :
buffer > 47
Cela peut paraitre bizarre car d'habitude, on ne peut pas comparer un caractere et un entier. Par exemple en python le code suivant retourne une erreur en nous disant qu'il ne peut pas comparer un str et un int :
Bac à sable Python 3.10.0
Il faut alors comprendre comment C voit les char. En C les deux variables suivantes ont la même valeur :
int a = 97; char b = 'a';
Et je le prouve :
Bac à sable C 10.2.0
Cela s'explique car 'a' en ASCII correspond a 97 en décimal :
Entrée
aRésultat
97Plutôt que de stocker les caracteres en soi, C stocke donc leur valeur ASCII.
Note : il fait quand même la distinction entre leurs types.
Dans le code de notre fonction, si buffer > 47 && buffer < 58 (soit buffer est entre 48 et 57 inclus), on lui enlève 48.
Or, 48 en base 10 équivaut à '0' en ascii et 57 en base 10 équivaut à '9' en ascii.
Entrée
48Résultat
0Entrée
57Résultat
9En clair, si le caractere entré est un entier de 0 à 9, sa valeur est changée en entier.
Par exemple, si on entre '4' :
Entrée
4Résultat
5252 > 47 && 52 < 58 est vrai, on enlève donc 48 à sa valeur et on obtient : buffer = 52 - 48 soit buffer = 4.
Ensuite, si buffer > 96 && buffer < 103 (soit buffer est entre 97 et 102 inclus), on lui enlève 87.
Entrée
97Résultat
aEntrée
102Résultat
fDonc si le caractere entré est une lettre de a à f, on lui soustrait 87.
Par exemple, si on entre 'c' :
Entrée
cRésultat
9999 > 96 && 99 < 103 est vrai, on enlève donc 87 à sa valeur et on obtient : buffer = 99 - 87 soit buffer = 12. Or :
Entrée
cRésultat
12Puis sinon (si buffer n'est pas dans 0-9 ou a-f) la fonction retourne 0.
En clair
Pour le moment, si le caractère entré dans l'entrée standard est valide en hexadécimal, il est converti en sa valeur héxadécimale. Sinon, la fonction retourne 0.
Ensuite, le programme ajoute pow(16, 15-j) * buffer à addr :
addr += pow(16, 15-j) * buffer;
Pour comprendre pourquoi, il faut revoir comment convertir de base 16 (héxadécimal) à base 10 (décimal). Pour cela, on prends l'example 0xfade :
On peut le décomposer en :
0xe + 0xd0 + 0xa00 + 0xf000
Puis :
| Hexadécimal | 0xe | 0xd0 | 0xa00 | 0xf000 |
|---|---|---|---|---|
| Conversion | ||||
| Résultat | 14 | 208 | 2560 | 61440 |
| Somme | 64222 |
Entrée
fadeRésultat
64222Pour convertir un nombre héxadécimal en décimal, on prends donc pour chaque caractere (en partant de la droite) 16**index * valeur. C'est exactement ce que fait pow(16, 15-j) * buffer mais en partant de la gauche.
Après tout cela
Le programme lis un caractere de plus et s'il est différent de '\n" (retour à la ligne), retourne 0. Cela assure donc une entrée de la forme :
"deadbeef1337babe\n" # Soit 16 caracteres héxadcimaux puis un retour a la ligne
A chaque répétition, il stocke chaque valeur dans la liste chain puis retourne 1 si toutes les entrées étaient correctes.