Tout d'abord et afin de mettre les choses au clair si vous avez lu le post de Thierry Zoller, le crack me n'est pas packé. Le début du code est standard et correspond à une application compilée avec VC (call XXXXXX | jmp long) et le reste du code est parfaitement normal.

Le main se situe en 004014A0, il affiche une message box dont la dialogproc se situe en 004013D0, encore une fois, rien de chiffré ni même d'obfusqué, l'entropie élevée de la fin de la section de data correspond en fait aux tables utilisées pour vérifier notre serial.

Enfin une chose à ne jamais faire est de remplacer un saut conditionnel par son inverse, si vous voulez que la branche soit toujours prise, il faut mettre un jmp, si vous ne voulez pas qu'elle soit prise, un nop. Remplacer un je par un jne reviens à remplacer la phrase "si le code est bon, affiche good boy" par "si le code est mauvais, affiche good boy", si l'on entre le bon sérial avec l'application patchée par Thierry Zoller on aura droit à un joli "bad boy"...

Passons maintenant à l'étude de la fonction de vérification qui se situe en 00401320. On se rend rapidement compte que le sérial doit faire 32 caractères : pour le bruteforce c'est mort :D, Le serial est ensuite converti en hexadécimal en prenant les caractères 2 par 2 ("1234AB" => 12 34 AB) puis ce tableaux de bytes est passé à la moulinette et est comparé à la chaine de caractère "hack.lu-2009-ctf", il va donc nous falloir coder la fonction inverse de la moulinette pour retrouver le code à entrer !

Cette "moulinette" me fait penser à une whitebox dans le sens cryptographique du terme, il n'y a pas de clef courte mais plutôt de grosses tables qui font penser aux tables générées pour chiffrer du DES plus rapidement bref ... Je vais maintenant essayer d'expliquer chacune des étapes de chiffrement et nous verrons ensuite comment les inverser :

Meli-Melo : permutation

La première fonction qui est utilisée sur le serial est une permutation dont la fonction inverse est la suivante :

  1. for (i = 0; i < 4 ; i++)
  2. {
  3. cpy[i] = chaine[i*4];
  4. cpy[i+4] = chaine[i*4+1];
  5. cpy[i+8] = chaine[i*4+2];
  6. cpy[i+12] = chaine[i*4+3];
  7. }

cette permutation est aussi utilisée juste avant la vérification du sérial, je ne m'attarde pas trop sur cette fonction qui est simplissime

*BOUM*

On passe maintenant à l'analyse de la seconde fonction qui est appliquée au sérial, là ça devient méchant, le crack-me effectue 8 fois une substitution + un ROL sur chacun des DWORD du serial + une "super" substitution, à chaque tour la première substitution est différente et est donnée par le n° de tour.

La substitution

Elle remplace le ième byte b du jème DWORD au nième tour par le tab_408138[(b+n*256)*16+j] comme je ne voulais pas m'embêter à dumper les tables, j'ai décidé d'écrire une DLL que j'injecte dans le crack me, je bruteforce ensuite chaque valeur pour obtenir son inverse par la substitution plutôt que de construire les tables inverses, c'est particulièrement porky mais bon ... Au final ça donne ceci (ce sont les fonction inverses) :

  1. BYTE getInv(int i,BYTE b, int j, int indice)
  2. {
  3. int k;
  4. for (k = 0; k < 0x100 ; k++)
  5. if (byte_408138[i+(k+indice)*16+j] == b)
  6. return k;
  7. MessageBoxA(0,"TAINPUUUUUU",0,0);
  8. DebugBreak();
  9. return 0;
  10. }
  11.  
  12. void substitution(PBYTE chaine, int indice)
  13. {
  14. int i;
  15. PBYTE v4 = chaine + 1;
  16.  
  17. for (i = 0; i < 16 ; i+=4)
  18. {
  19. v4[i- 1] = getInv(0,v4[i- 1],i,indice);
  20. v4[i] = getInv(1,v4[i],i,indice);
  21. v4[i+1] = getInv(2,v4[i+ 1],i,indice);
  22. v4[i+2] = getInv(3,v4[i+ 2],i,indice);
  23. }
  24. }

le ror

La seconde fonction utilisée sur le serial dans la grande boucle effectue un simple ROR sur chacun des DWORD du serial suivant le n° de ce DWORD, la fonction inverse est la suivante :

  1. void rocknrol(PDWORD chaine)
  2. {
  3. int i;
  4. for (i = 3; i >= 0 ; i--)
  5. chaine[i] = ROL(chaine[i],8*i);
  6. }

je ne m'attarderai pas dessus, je pense que tout le monde a compris :D

la "super" substitution

J'appelle cette fonction la super subtitution parce qu'elle ne se contente pas d'associer à un byte un autre byte mais d'associer à 4 bytes, 4 autres bytes suivant la fonction suivante :

  1. for (i = 0; i < 4 ; i++)
  2. {
  3. ser0 = serial[i];
  4. ser1 = serial[i+4];
  5. ser2 = serial[i+8];
  6. ser3 = serial[i+16];
  7. serial[i] = 414000[4 * ser0] ^ 414400[4 * ser1] ^ 414800[4 * ser2] ^ 414C00[4 * ser3];
  8. serial[i+4] = 414001[4 * ser0] ^ 414401[4 * ser1] ^ 414801[4 * ser2] ^ 414C01[4 * ser3];
  9. serial[i+8] = 414000[4 * ser0 + 2] ^ 414400[4 * ser1 + 2] ^ 414802[4 * ser2] ^ 414C00[4 * ser3 + 2];
  10. serial[i+16] = 414000[4 * ser0 + 3] ^ 414400[4 * ser1 + 3] ^ 414800[4 * ser2 + 3] ^ 414C00[4 * ser3 + 3];
  11. }

Comme on le voit ici chaque valeur après substitution dépend des 4 valeurs originales on ne peut donc pas construire de table inverse byte par byte (si il fallait construire une table ce serait une correspondance de DWORD à DWORD et on aurait un immense table) la solution est donc de fixer 3 valeurs, d'en déduire la 4eme et de vérifier que cela concorde.

Après étude, on se rend compte que la valeur donnée par 414C00[4 * ser3 + 3] correspond à une substitution identité, c'est à dire qu'elle associe à la valeur ser3 .... ser3 ! On choisit donc de fixer ser0, ser1 et ser2 et de vérifier si la valeur de ser3 donnée par ser3 = serial[i+16] ^ 414000[4 * ser0 + 3] ^ 414400[4 * ser1 + 3] ^ 414800[4 * ser2 + 3] est juste. La fonction inverse à cette substitution est donc :

  1. void bfser(PBYTE chaine)
  2. {
  3. DWORD i,j,k;
  4. for(i = 0; i <= 0xFF ; i++)for(j = 0; j <= 0xFF ; j++)for(k = 0; k <= 0xFF ; k++)
  5. {
  6. BYTE a,b,c,d,l;
  7. a = byte_414000[4 * i] ^ byte_414400[4 * j] ^ byte_414800[4 * k];
  8. b = byte_414001[4 * i] ^ byte_414401[4 * j] ^ byte_414801[4 * k];
  9. c = tab0[4 * i + 2] ^ tab1[4 * j + 2] ^ algn_414802[4 * k];
  10. d = tab0[4 * i + 3] ^ tab1[4 * j + 3] ^ tab2[4 * k + 3];
  11. l = a^chaine[-4];
  12. if (((byte_414C01[4 * l]^b) == chaine[0]) &&
  13. ((tab3[4 * l+2]^c) == chaine[4]) &&
  14. ((tab3[4 * l+3]^d) == chaine[8]))
  15. {
  16. chaine[-4] = i;
  17. chaine[0] = j;
  18. chaine[4] = k;
  19. chaine[8] = l;
  20. return;
  21. }
  22. }
  23. MessageBoxA(0,"ERRRROOOOOR",0,0);
  24. }
  25.  
  26. /* ouais ouais je sais j'étais pas inspiré ... */
  27. void lolzilolzion(PBYTE chaine)
  28. {
  29. PBYTE magictab = chaine + 4;
  30. int i;
  31.  
  32. for(i = 4; i ; i--)
  33. {
  34. bfser(magictab);
  35. ++magictab;
  36. }
  37. log("fin bfser :",chaine);
  38. }

et la fonction inverse la grosse substitution est donnée par :

  1. void secondfun(PBYTE chaine)
  2. {
  3. int i;
  4.  
  5. // on oublie pas de faire décroître l'indice et non de le faire croître comme dans la fonction originale
  6. for (i = 8; i >= 0 ; i --)
  7. {
  8. lolzilolzion(chaine);
  9. rocknrol((DWORD*)chaine);
  10. substitution(chaine,i*0x100);
  11. }
  12. }

Dernier pas

Les dernières opérations sont une substitution, à nouveau un ror des valeurs et enfin la même substitution que celle du début. Une fois tout ces éléments en main, il suffit d'utiliser nos fonctions inverses dans l'ordre inverse de celui du chiffrement et on obtient la fonction de déchiffrement. Voici le code complet de mon "keygen" :

  1. #include "main.h"
  2. #include <windows.h>
  3.  
  4. #define ROL(a,b) ((a << b) | (a >> (32 - b)))
  5.  
  6. #define tab3 ((BYTE*)0x414C00)
  7. #define tab2 ((PBYTE)0x414800)
  8. #define tab1 ((PBYTE)4277248)
  9. #define tab0 ((PBYTE)4276224)
  10. #define byte_414000 ((PBYTE)0x414000)
  11. #define byte_414400 ((PBYTE)0x414400)
  12. #define byte_414800 ((PBYTE)0x414800)
  13. #define byte_414001 ((PBYTE)0x414001)
  14. #define byte_414401 ((PBYTE)0x414401)
  15. #define byte_414801 ((PBYTE)0x414801)
  16. #define byte_414C01 ((PBYTE)0x414C01)
  17. #define algn_414802 ((PBYTE)0x414802)
  18. #define byte_414C01 ((PBYTE)0x414C01)
  19. #define byte_408138 ((PBYTE)0x408138)
  20. #define byte_408139 ((PBYTE)0x408139)
  21. #define byte_40813A ((PBYTE)0x40813A)
  22. #define stru_40813B ((PBYTE)0x40813B)
  23. #define byte_411138 ((PBYTE)0x411138)
  24. #define byte_411139 ((PBYTE)0x411139)
  25. #define byte_41113A ((PBYTE)0x41113A)
  26. #define stru_41113B ((PBYTE)0x41113B)
  27.  
  28. void log(char* mess, PBYTE chaine)
  29. {
  30. char resultat[40];
  31. static HANDLE hFile = NULL;
  32. int i;
  33. DWORD NumberOfBytesWritten;
  34. if (! hFile)
  35. hFile = CreateFileA("youpi.txt",(GENERIC_READ | GENERIC_WRITE),FILE_SHARE_READ,NULL,CREATE_ALWAYS,0,NULL);
  36. for (i = 0; i < 16 ; i++)
  37. {
  38. wsprintfA(&resultat[2*i],"%02X",chaine[i]);
  39. }
  40. WriteFile(hFile,mess,strlen(mess),&NumberOfBytesWritten,NULL);
  41. WriteFile(hFile,"\x0D\x0A",2,&NumberOfBytesWritten,NULL);
  42. WriteFile(hFile,resultat,33,&NumberOfBytesWritten,NULL);
  43. WriteFile(hFile,"\x0D\x0A",2,&NumberOfBytesWritten,NULL);
  44.  
  45.  
  46. }
  47.  
  48. void bfser(PBYTE chaine)
  49. {
  50. DWORD i,j,k;
  51. for(i = 0; i <= 0xFF ; i++)for(j = 0; j <= 0xFF ; j++)for(k = 0; k <= 0xFF ; k++)
  52. {
  53. BYTE a,b,c,d,l;
  54. a = byte_414000[4 * i] ^ byte_414400[4 * j] ^ byte_414800[4 * k];
  55. b = byte_414001[4 * i] ^ byte_414401[4 * j] ^ byte_414801[4 * k];
  56. c = tab0[4 * i + 2] ^ tab1[4 * j + 2] ^ algn_414802[4 * k];
  57. d = tab0[4 * i + 3] ^ tab1[4 * j + 3] ^ tab2[4 * k + 3];
  58. l = a^chaine[-4];
  59. if (((byte_414C01[4 * l]^b) == chaine[0]) &&
  60. ((tab3[4 * l+2]^c) == chaine[4]) &&
  61. ((tab3[4 * l+3]^d) == chaine[8]))
  62. {
  63. chaine[-4] = i;
  64. chaine[0] = j;
  65. chaine[4] = k;
  66. chaine[8] = l;
  67. return;
  68. }
  69. }
  70. MessageBoxA(0,"ERRRROOOOOR",0,0);
  71. }
  72.  
  73.  
  74. void lolzilolzion(PBYTE chaine)
  75. {
  76. PBYTE magictab = chaine + 4;
  77. int i;
  78.  
  79. for(i = 4; i ; i--)
  80. {
  81. bfser(magictab);
  82. /* ser0 = magictab[-4];
  83.   ser1 = magictab[0];
  84.   ser2 = magictab[4];
  85.   ser3 = magictab[8];
  86.   new0 = byte_414000[4 * ser0] ^ byte_414400[4 * ser1] ^ byte_414800[4 * ser2] ^ byte_414C00[4 * ser3];
  87.   new1 = byte_414001[4 * ser0] ^ byte_414401[4 * ser1] ^ byte_414801[4 * ser2] ^ byte_414C01[4 * ser3];
  88.   new2 = tab0[4 * ser0 + 2] ^ tab1[4 * ser1 + 2] ^ algn_414802[4 * ser2] ^ tab3[4 * ser3 + 2];
  89.   new3 = tab0[4 * ser0 + 3] ^ tab1[4 * ser1 + 3] ^ tab2[4 * ser2 + 3] ^ tab3[4 * ser3 + 3];
  90.   magictab[-4] = new0;
  91.   magictab[0] = new1;
  92.   magictab[4] = new2;
  93.   magictab[8] = new3;*/
  94. ++magictab;
  95. }
  96. log("fin bfser :",chaine);
  97. }
  98.  
  99. BYTE getInv(int i,BYTE b, int j, int indice)
  100. {
  101. int k;
  102. for (k = 0; k < 0x100 ; k++)
  103. if (byte_408138[i+(k+indice)*16+j] == b)
  104. return k;
  105. MessageBoxA(0,"ARZOOOOOO",0,0);
  106. DebugBreak();
  107. return 0;
  108. }
  109.  
  110. void substitution(PBYTE chaine, int indice)
  111. {
  112. int i;
  113. PBYTE v4 = chaine + 1;
  114.  
  115. for (i = 0; i < 16 ; i+=4)
  116. {
  117. v4[i- 1] = getInv(0,v4[i- 1],i,indice);
  118. v4[i] = getInv(1,v4[i],i,indice);
  119. v4[i+1] = getInv(2,v4[i+ 1],i,indice);
  120. v4[i+2] = getInv(3,v4[i+ 2],i,indice);
  121. }
  122. }
  123.  
  124. void rocknrol(PDWORD chaine)
  125. {
  126. int i;
  127. for (i = 3; i >= 0 ; i--)
  128. chaine[i] = ROL(chaine[i],8*i);
  129. }
  130.  
  131. void secondfun(PBYTE chaine)
  132. {
  133. int i;
  134.  
  135. for (i = 8; i >= 0 ; i --)
  136. {
  137. lolzilolzion(chaine);
  138. rocknrol((DWORD*)chaine);
  139. substitution(chaine,i*0x100);
  140. log("substitution :",chaine);
  141. }
  142. }
  143.  
  144. void firstfun(PBYTE chaine)
  145. {
  146. int i;
  147. PBYTE v4 = chaine + 1;
  148.  
  149. for (i = 0; i < 16 ; i+=4)
  150. {
  151. v4[i- 1] = getInv(36864,v4[i- 1],i,0);
  152. v4[i] = getInv(36865,v4[i],i,0);
  153. v4[i+1] = getInv(36866,v4[i+ 1],i,0);
  154. v4[i+2] = getInv(36867,v4[i+ 2],i,0);
  155. }
  156. log("fin first fun :",chaine);
  157. }
  158.  
  159. void DLL_EXPORT getIt()
  160. {
  161. BYTE chaine[] = {0x68,0x61,0x63,0x6B,0x2E,0x6C,0x75,0x2D,0x32,0x30,0x30,0x39,0x2D,0x63,0x74,0x66};
  162. BYTE cpy[0x10];
  163. int i;
  164. DebugBreak();
  165. for (i = 0; i < 4 ; i++)
  166. {
  167. cpy[i] = chaine[i*4];
  168. cpy[i+4] = chaine[i*4+1];
  169. cpy[i+8] = chaine[i*4+2];
  170. cpy[i+12] = chaine[i*4+3];
  171. }
  172.  
  173. log("transfo1 :",cpy);
  174. rocknrol((DWORD*)cpy);
  175. log("rocknrol :",cpy);
  176. firstfun(cpy);
  177. log("firstfun :",cpy);
  178. secondfun(cpy);
  179. for (i = 0; i < 4 ; i++)
  180. {
  181. chaine[i*4] = cpy[i];
  182. chaine[i*4+1] = cpy[i+4];
  183. chaine[i*4+2] = cpy[i+8];
  184. chaine[i*4+3] = cpy[i+12];
  185. }
  186. log("résultat :",chaine);
  187. MessageBoxA(0,"youpi !","YE SOUIS DIABOULIQUE !!!",0);
  188. }
  189.  
  190.  
  191.  
  192. // a sample exported function
  193. void DLL_EXPORT SomeFunction(const LPCSTR sometext)
  194. {
  195. MessageBoxA(0, sometext, "DLL Message", MB_OK | MB_ICONINFORMATION);
  196. }
  197.  
  198. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
  199. {
  200. switch (fdwReason)
  201. {
  202. case DLL_PROCESS_ATTACH:
  203. DebugBreak();
  204. getIt();
  205. break;
  206.  
  207. case DLL_PROCESS_DETACH:
  208. // detach from process
  209. break;
  210.  
  211. case DLL_THREAD_ATTACH:
  212. // attach to thread
  213. break;
  214.  
  215. case DLL_THREAD_DETACH:
  216. // detach from thread
  217. break;
  218. }
  219. return TRUE; // succesful
  220. }

Une fois le code compilé et injecté dans le keygen me, la dll créée un fichier de log contenant les différentes étapes du calcul ainsi que le serial : 192EF9E61164BD289F773E6C9101B89C

On est donc content et on envoie tout ça ! Au final il m'a fallut 5 heures pour venir à bout de ce crack me tout mignon :D merci J-B pour ce keygen-me qui m'a donné l'occasion de faire un peu autre chose que de l'unpacking ;). Un grand merci aussi à l'équipe de Hack.lu pour ce challenge et pour le T-Shirt de la conf et l'abonnement à MISC (que je n'ai pas encore reçu mais bon, je ne désespère pas :D )