On lance le binaire pour voir :
$ file babyfuscation
babyfuscation: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=80e2baa56f7f889a55c075839bfbb875d883476b, for GNU/Linux 3.2.0, not stripped
$ ./babyfuscation
Enter the flag:
TOTO
Wrong flag. Try again!
J’ouvre le binaire dans Ghidra et je cherche la fonction main
:
void main(void)
{
int local_14;
int local_10;
int local_c;
for (local_14 = 0; local_14 < 0x50; local_14 = local_14 + 1) {
VYeXkgjLLMrczyw7i7dJPkyAbxqgCahe[local_14] = VYeXkgjLLMrczyw7i7dJPkyAbxqgCahe[local_14] ^ 0x42;
}
for (local_10 = 0; local_10 < 0x50; local_10 = local_10 + 1) {
a93rEUcvwf4Ec9KHKqzFx7wL[local_10] = a93rEUcvwf4Ec9KHKqzFx7wL[local_10] ^ 0x13;
}
for (local_c = 0; local_c < 0x50; local_c = local_c + 1) {
ouPrjEhgqPVNXCqchuzw7WTWLHnkbwqj[local_c] = ouPrjEhgqPVNXCqchuzw7WTWLHnkbwqj[local_c] ^ 0x37;
}
VsvYbpipYYgRoCeFtoxhtAmdFuNu3WvV();
wKtyPoT4WdyrkVzhvYUfvqo3M9iPVMd3();
VakkEeHbtHMpNqXPMkadR4v7K();
return;
}
En effet, cela semble obfusqué ! Les chaînes et les noms de fonctions en
tout cas. Je vais commencer par décoder les chaînes. Je pourrais les
décoder en python par exemple, mais j’ai la flemme, je lance le binaire
dans gdb
, je pose un point d’arrêt après le déchiffrement et je dumpe
les valeurs :
gef➤ x/s 0x555555558080
0x555555558080 <VYeXkgjLLMrczyw7i7dJPkyAbxqgCahe>: "Enter the flag: "
gef➤ x/s 0x5555555580e0
0x5555555580e0 <a93rEUcvwf4Ec9KHKqzFx7wL>: "Correct! You've found the flag!"
gef➤ x/s 0x555555558140
0x555555558140 <ouPrjEhgqPVNXCqchuzw7WTWLHnkbwqj>: "Wrong flag. Try again!"
Je renomme les chaînes dans Ghidra, puis j’affecte des noms des fonctions
en analysant leur code. Cela me donne rapidement un code plus
compréhensible. D’abord le main
:
void main(void)
{
int i;
int j;
int k;
for (i = 0; i < 0x50; i = i + 1) {
s_Enter_flag[i] = s_Enter_flag[i] ^ 0x42;
}
for (j = 0; j < 0x50; j = j + 1) {
s_Correct[j] = s_Correct[j] ^ 0x13;
}
for (k = 0; k < 0x50; k = k + 1) {
s_Wrong[k] = s_Wrong[k] ^ 0x37;
}
input_flag();
encode_input_flag();
check_result();
return;
}
Le plus important, c’est ce qui est fait dans encode_input_flag()
.
Ce code est exécuté sur le buffer ayant reçu le flag que l’utilisateur
a saisi :
void encode_input_flag(void)
{
int iVar1;
int i;
iVar1 = len(input_buffer);
for (i = 0; i < iVar1 + 1; i = i + 1) {
encoded_flag_buffer[i] =
(char)i * '\x03' + 0x1fU ^
(input_buffer[i] * '\b' | (char)input_buffer[i] >> 5);
}
encoded_flag_buffer[iVar1 + 1] = 0;
return;
}
Puis notre input modifié est comparé avec un tableau fixe :
bool check_result(void)
{
int iVar1;
iVar1 = strcmp?(encoded_flag_buffer, proper_result);
if (iVar1 != 0) {
puts(s_Wrong);
}
else {
puts(s_Correct);
}
return iVar1 == 0;
}
Le tableau fixe proper_result
pointe sur 2d 38 bf 32 f0 05 a8 b5 04 9b 8c 53 ca e7 f0 67 f6 59 c4 f1 50 e7 7a a5 74 ab dc d9 50 f7 5a bd b6 2b 9e 31 90 37 08 1d 3e a9 2c 69 0a 67 38 9f 0e 2b 24 93 72 1f 40 6d d4 7b ee 51 1a 4f ca 6d ec f1 24 cb 72 05 f1
.
Ainsi, il faut inverser (si possible) le traitement réalisé dans
encode_input_flag()
et l’appliquer au buffer proper_result
pour obtenir
le flag.
On reprend la routine appliquée à chaque caractère du flag dans Ghidra :
(char)i * '\x03' + 0x1fU ^ (input_buffer[i] * '\b' | (char)input_buffer[i] >> 5);
On simplifie en retirant les cast inutiles et en traduisant les valeurs sous forme d’entiers :
( i * 3 + 0x1f ) ^ ( flag[i] * 8 | flag[i] >> 5)
Et on peut encore améliorer en remarquant qu’une multiplication par un multiple de deux (resp, division) peut aussi être représenté par un décalage à gauche (resp, à droite) des bits :
( i * 3 + 0x1f ) ^ ( flag[i] << 3 | flag[i] >> 5)
Et voilà un calcul simple ! Les bits de poids faibles (3) et de poids forts (5) sont inversés et XORés avec une valeur dépendant de l’indice de la boucle. Il suffit d’inverser les opérations, et cela se fait trivialement en python :
>>> data = [ int(x, 16) for x in "2d 38 bf 32 f0 05 a8 b5 04 9b 8c 53 ca e7 f0 67 f6 59 c4 f1 50 e7 7a a5 74 ab dc d9 50 f7 5a bd b6 2b 9e 31 90 37 08 1d 3e a9 2c 69 0a 67 38 9f 0e 2b 24 93 72 1f 40 6d d4 7b ee 51 1a 4f ca 6d ec f1 24 cb 72 05 f1".split(" ") ]
>>> data = [ (i*3+0x1f)^c for i, c in enumerate(data) ]
>>> data = [ (c << 5 | c >> 3) & 0xff for c in data ]
>>> bytes(data)
b'FCSC{e30f46b147e7a25a7c8b865d0d895c7c7315f69582f432e9405b6d093b6fb8d3}\x00'
Le doute n’est pas permis, mais on vérifie quand même !
$ ./babyfuscation
Enter the flag:
FCSC{e30f46b147e7a25a7c8b865d0d895c7c7315f69582f432e9405b6d093b6fb8d3}
Correct! You've found the flag!