iCTF 2011 > Challenge 30 - Reverse 500
Ce challenge était l'un des plus prolifiques (500 points). Techniquement il n'était pas dur, seulement l'exécutable n'était pas présenté tel quel.
Etait distribué un fichier reverse2.7z.enc. Comme le but principal semblait quand même être du reverse, on se dit que l'encryption ne doit pas être bien compliquée. On sait que le fichier encrypté est un 7z. Un petit coup d'oeil à la documentation 7z nous donne le format du header, qui commence par "7z\xBC\xAF\x27\x1C". On passe en revue les encryptions bateau et on en arrive à tester le XOR :
#!/usr/bin/python zhead = "7z\xBC\xAF\x27\x1C" f = open("/tmp/reverse2.7z.enc") str = f.read(len(zhead)) for i in range(0, len(zhead)): print hex(ord(str[i]) ^ ord(zhead[i])),
Ce qui rend "0x7a 0x34 0x12 0x90 0x7a 0x34". On voit que la clé commence à se répéter, ce qui confirme bien un encryptage XOR avec la clé 0x7a341290. On peut donc ensuite décompresser le 7z.
Le 7z est composé de deux petits fichiers a et b. En passant les deux à l'hexdump, on arrive facilement à deviner leur nature :
Par conséquent, on essaye de désassembler a. L'idéal serait d'avoir IDA Pro sous la main, mais ma version de Wine ne semble pas vouloir afficher correctement le code désassemblé.. Bref, x86dis -s att -e 0 < a nous sort une première fonction entre 0 et 0x75. On renouvelle l'opération à partir de l'offest 0x76 et on obtient une deuxième fonction qui court jusqu'à la fin du fichier.
Je ne vais pas aller très en détail ici car les deux fonctions sont vraiment courtes et simples. La première effectue une boucle entre 0 et 0x18 sur les bytes du buffer passé en argument. Elle appelle une fonction à 0x6001A238 3 fois :
La deuxième a également un unique buffer en argument qu'elle remplit en xorant un byte sur 2 d'un autre buffer à 0x60011288 avec son indice. Le piège était sûrement de penser qu'une fonction appellait l'autre (ce que je pensais avant de voir que la deuxième n'avait qu'un argument). Pour comprendre quelle est la fonction à 0x6001A238, on regarde ce qu'on peut trouver dans le segment données et on trouve rapidement que si le début de b correspond à 0x60011260, les trois appels se traduisent en :
On sait donc que 0x6001A238 est l'adresse du pointeur vers printf() et que cette fonction ne fait qu'afficher le numéro de compte selon la clé passée en paramètre. Comme la deuxième fonction semble remplir un buffer de même taille, on devine qu'il sagit de la fameuse clé.
A partir de là, deux façons de faire : le code est petit et simple, on peut donc recoder ces deux fonctions en C ; on peut également reprendre les fonctions telles quelles et juste modifier les pointeurs vers les données et vers printf(). Personnellement je trouve ça plus marrant :
#include <stdio.h> #include <sys/mman.h> #include <string.h> char data[] = "\x2d\x00\x00\x00\x25\x63\x00\x00\x42\x61\x6e\x6b\x20\x61\x63\x63\x6f\x75\x6e\x74\x3a\x20\x00\x00" "\x47\x92\x1f\x1c\xc9\x4d\xa3\x97\x04\xc1\x43\x63\xe5\x20\xee\xce\x1f\x82\x9d\x23\x3f\x5f\x5c\x59" "\x17\xf7\x95\xaf\xec\x2b\xc6\x5e\x11\xeb\x33\xfe\x35\xf6\xf7\x87\x28\xaa\xa0\x32\xce\xa9\x2b\x40" "\x5e\x42\xcd\x53\xe7\x07\xd2\x8d\x98\x01\xa1\xe2\x12\x37\x54\xea"; char print_func[] = "\x55\x8b\xec\x83\xec\x44\x53\x56\x57\xc7\x45\xfc\x00\x00\x00\x00\x68" "\x68\x12\x01\x60" // offset 0x11 - "Bank account" = @data + 8 "\xff\x15" "\x38\xa2\x01\x60" // offset 0x17 - @printf "\x83\xc4\x04\xc7\x45\xfc\x00\x00\x00\x00\xeb\x09\x8b\x45\xfc\x83\xc0\x01\x89\x45\xfc\x83\x7d" "\xfc\x18\x73\x39\x8b\x45\x08\x03\x45\xfc\x0f\xb6\x00\x99\xb9\x0a\x00\x00\x00\xf7\xf9\x83\xc2\x30\x52\x68" "\x64\x12\x01\x60" // offset 0x4c - @data + 4 "\xff\x15" "\x38\xa2\x01\x60" // offset 0x52 - @printf "\x83\xc4\x08\x83\x7d\xfc\x0c\x75\x0e\x68" "\x60\x12\x01\x60" // offset 0x60 - @data "\xff\x15" "\x38\xa2\x01\x60" // offset 0x66 - @printf "\x83\xc4\x04\xeb\xb8\x5f\x5e\x5b\x8b\xe5\x5d\xc3"; char compute_func[] = "\x55\x8b\xec\x83\xec\x44\x53\x56\x57\xc7\x45\xfc\x00\x00\x00\x00\xc7\x45\xfc\x00\x00\x00\x00" "\xeb\x09\x8b\x45\xfc\x83\xc0\x01\x89\x45\xfc\x83\x7d\xfc\x18\x73\x0d\x8b\x45\x08\x03\x45\xfc\x8a" "\x4d\xfc\x88\x08\xeb\xe4\xc7\x45\xfc\x00\x00\x00\x00\xeb\x09\x8b\x45\xfc\x83\xc0\x01\x89\x45\xfc" "\x83\x7d\xfc\x18\x73\x20\x8b\x45\x08\x03\x45\xfc\x0f\xb6\x08\x8b\x55\xfc\x0f\xb6\x04\x55" "\x88\x12\x01\x60" // offset 0xfd - 0xa0 = 93 - @data + 0x28 "\x33\xc8\x8b\x55\x08\x03\x55\xfc\x88\x0a\xeb\xd1\x5f\x5e\x5b\x8b\xe5\x5d\xc3"; void copy_le(char * dst, unsigned int addr) { *(dst++) = addr & 0xff; *(dst++) = (addr >> 8) & 0xff; *(dst++) = (addr >> 16) & 0xff; *dst = (addr >> 24) & 0xff; } int main() { void * prog = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); void (* pf)(char *) = prog; void (* cf)(char *) = prog + 0x100; char buf[0x18]; void * printfaddr = printf; memcpy(prog, print_func, sizeof(print_func)); memcpy(prog + 0x100, compute_func, sizeof(compute_func)); memcpy(prog + 0x200, data, sizeof(data)); /* Change old pointers */ copy_le(prog+0x100 + 93, (unsigned int)(prog + 0x228)); copy_le(prog+0x11, (unsigned int)(prog + 0x208)); copy_le(prog+0x4c, (unsigned int)(prog + 0x204)); copy_le(prog+0x60, (unsigned int)(prog + 0x200)); copy_le(prog+0x17, (unsigned int)&printfaddr); copy_le(prog+0x52, (unsigned int)&printfaddr); copy_le(prog+0x66, (unsigned int)&printfaddr); cf((char *)&buf); pf((char *)&buf); return 0; }
Ensuite, il faut juste faire attention à bien l'exécuter en 32-bits :
$ gcc -m32 rev30.c -o rev30 && ./rev30 Bank account: 1615944358326-32680530047