Unpacking du binaire

Je commence tout de suite par préciser que cette étape n'est pas du tout nécessaire. Si vous avez la chance d'avoir le crack-me qui se lance chez vous (ce qui n'était pas le cas chez moi ...) vous avez (presque) directement accès au code du crackme. Malgré tout comme je me suis embêter à unpacker le binaire autant m'embêter à vous expliquer comment, de plus ca me donne l'occasion de publier dans un cadre légal mon superbe call fixer ;)

Des l'ouverture du binaire sous olly on se rend compte qu'il est protégé par asprotect, ceux qui ont déja eu affaire à asprotect comprendront pourquoi ... Bref, après avoir un peu pesté sur les organisateurs du ctf qui utilisent des packers commerciaux j'ai commencé à unpacker la bête. Heureusement seul la protection des imports est utilisée ici, il est donc très rapide de retrouver l'oep de la cible et de fixer l'exécutable (ce qui n'aurait pas été le cas en cas de virtualisation à outrance du code, protection custom de asprotect que je n'ai pas encore réussit à casser pour le moment :'( ).

Une dernière chose, si le binaire est exécuté plus de 5 ou 10 fois, il refuse de se lancer et nous affiche une jolie MessageBox "OMG CTF OVERLORD JUST PRAWNED J00, NO MOAR RUNZ" "Unregistered Version", pour pouvoir le relancer je ne me suis pas trop embêté et ai utilisé Trial Reset (un petit outil bien pratique permettant comme son nom l'indique de remettre à 0 les compteurs de plusieurs packers)

Retrouver l'OEP

L'OEP (ou Original Entry Point, c'est à dire l'entry point du programme avant qu'il ne soit protégé pour les novices en unpacking) se retrouve très facilement en utilisant la méthode bien connue de remontée de la pile (vous verrez c'est comme le planté de bâton, s'tout con).

C'est la méthode la plus simple pour retrouver un OEP sans se casser la tête, elle ne fonctionne que pour les packers qui ne détruisent pas les premières instructions du programme, elle consiste tout simplement à lancer le programme (sous olly ou alors normalement puis en s'attachant au programme) puis à le mettre en pause (ici on attendra qu'il se termine tout simplement), il suffit alors de regarder dans la pile la succession d'appels qui ont été fait et de retrouver où la première fonction a été appelée, à partir de là il suffit de remonter un peu dans le code pour retrouver l'OEP.

0012FED4  ]0012FEE8
0012FED8  |00401764  RETURN to ctf_bin5.00401764 from ctf_bin5.00401627  <- 2ème fonction dans notre call stack
0012FEDC  |FFFFFFFF
0012FEE0  |00000000
0012FEE4  |00000000
0012FEE8  \0012FF6C
0012FEEC   004012F6  RETURN to ctf_bin5.004012F6 from ctf_bin5.00401753  <- ici se trouve notre call
0012FEF0   FFFFFFFF
0012FEF4   42887815
0012FEF8   01A2009E  RETURN to 01A2009E   <- correspond à un call du protector dans la mémoire allouée, ce n'est donc pas notre première fonction
0012FEFC   BB10C334

Ainsi dans notre cas, nous voyons dans la pile que la première fonction à être appelée (dans notre call stack) l'est depuis l'adresse 004012F6. A cette adresse on reconnait clairement le code généré par VC il nous suffit donc de remonter un peu dans le code et de chercher le jmp long sautant sur le début de la fonction.

  1. 004012D2 0FB74D C8 MOVZX ECX,WORD PTR [EBP-38]
  2. 004012D6 EB 03 JMP SHORT 004012DB
  3. 004012D8 6A 0A PUSH 0A
  4. 004012DA 59 POP ECX
  5. 004012DB 51 PUSH ECX
  6. 004012DC 50 PUSH EAX
  7. 004012DD 56 PUSH ESI
  8. 004012DE 68 00004000 PUSH 00400000
  9. 004012E3 E8 18FDFFFF CALL <ModuleEntryPoint>
  10. 004012E8 8945 E0 MOV [EBP-20],EAX
  11. 004012EB 3975 E4 CMP [EBP-1C],ESI
  12. 004012EE 75 06 JNZ SHORT 004012F6
  13. 004012F0 50 PUSH EAX
  14. 004012F1 E8 5D040000 CALL 00401753
  15. 004012F6 E8 84040000 CALL 0040177F
  16. 004012FB 897D FC MOV [EBP-4],EDI

Nous trouvons donc que l'OEP se situe en 00401353 où nous retrouvons le début typique d'un code compilé avec VC :

  1. 00401353 . E8 04170000 CALL 00402A5C
  2. 00401358 .^ E9 78FEFFFF JMP 004011D5

Fixer les imports

Asprotect ne se contente pas de compresser / chiffrer le code de l'exécutable, il protège aussi ses imports en remplaçant ses call [API] par des call Asprotect | garbage instruction (garbage instruction étant une instruction de 1 byte destinée à obtenir la même taille de code qu'un call []). De plus il élimine de l'IAT certaines adresses ce qui complique encore un peu plus notre tâche :

  1. ; call redirigé :
  2. 004011E6 . 8D45 98 LEA EAX,[EBP-68]
  3. 004011E9 . 50 PUSH EAX
  4. 004011EA . E8 11EE6301 CALL 01A40000 ; 01A40000 pointe ici sur une zone mémoire allouée
  5. 004011EF . 97 XCHG EAX,EDI
00408008  7C8260C2  kernel32.FreeResource
0040800C  7C80A055  kernel32.LoadResource
00408010  A494E800  <- une adresse supprimée 
00408014  CC2AE72B  <- une autre ...
00408018  7C80BD09  kernel32.SizeofResource

Heureusement il est possible de retrouver ses adresse grâce à une méthode dont j'ai appris l'existence grâce à ulysse_31 (merci à toi ;)) sans cette méthode il est bien plus compliqué de reconstruire l'IAT. Asprotect lors de la protection de l'exécutable remplace les RVA de l'IAT par des CRC du nom des APIs correspondante, lors de l'exécution du code du loader, il va rechercher les adresses de ces APIs à l'aide le leur CRC et les placer dans l'IAT. L'erreur de Alexeï Solodovnikov a été d'écraser les adresses des APIs éliminée par une valeur aléatoire mais par un CRC modifié du nom de l'API, pour retrouver les adresses correspondantes il suffit donc de modifier légèrement le comportement du loader pour qu'ils retrouve les adresses de toutes les APIs et non uniquement celles qui devraient l'être, ce qui est fait à l'aide de l'ollyscript suivant (désolé pour les noms des variables ...) :

  1. var HBPJMP
  2. var HBPCLEF
  3. var HBPOEP
  4. var GetProc
  5. var ROKXA
  6. var HBPMAGICLOOP
  7.  
  8. var pourri
  9. var moisie
  10.  
  11.  
  12. gpa "VirtualAllocEx" , "kernel32.dll"
  13. mov va , $RESULT
  14.  
  15. bphws va , "x"
  16. LOOPLOL:
  17. run
  18. cmp eip , va
  19. jne LOOPLOL
  20. findmem #5356578B55148B5D088DBDFAFEFFFF8BC24883E8020FB6308B451083E8020FB600#
  21. cmp $RESULT , 0
  22. je LOOPLOL
  23.  
  24. bphwc va
  25.  
  26. mov HBPJMP , $RESULT
  27. add HBPJMP , 33
  28.  
  29. mov HBPCLEF , HBPJMP
  30. add HBPCLEF , 3A
  31.  
  32. mov GETPROCADD , HBPJMP
  33. add GETPROCADD , 1A1
  34.  
  35. mov HBPOEP , 00401353
  36.  
  37. bphws HBPCLEF , "x"
  38. bphws HBPJMP , "x"
  39. bphws HBPOEP , "x"
  40.  
  41. LOL:
  42. run
  43. cmp eip , HBPJMP
  44. jne LOL
  45.  
  46. MEGALOOP:
  47. cmp eip , HBPJMP
  48. jne pasapi
  49. cmp !ZF , 0
  50. jne paspourri
  51. mov moisie , ebx
  52. add moisie , 39
  53. mov moisie , [moisie]
  54. and moisie , 0FF
  55. cmp moisie , esi
  56. jne vraimentpourri
  57. sti
  58. go GETPROCADD
  59. mov [edx] , GetProc
  60. sti
  61. run
  62. jmp MEGALOOP
  63. vraimentpourri:
  64. mov !ZF , 1
  65. mov pourri , 1
  66. sti
  67. run
  68. jmp MEGALOOP
  69. paspourri:
  70. mov pourri , 0
  71. run
  72. jmp MEGALOOP
  73. pasapi:
  74. cmp eip , HBPCLEF
  75. jne pasjmp
  76. cmp pourri , 1
  77. je bienpourri
  78. sti
  79. run
  80. jmp MEGALOOP
  81. bienpourri:
  82. sub ecx , 0A
  83. sti
  84. run
  85. jmp MEGALOOP
  86. pasjmp:
  87. cmp eip , HBPOEP
  88. jne BLEM
  89.  
  90. bphwc HBPJMP
  91. bphwc HBPOEP
  92. bphwc HBPCLEF
  93. ret

Maintenant que nous avons une IAT toute propre, il nous faut fixer les call redirigés par asprotect. Pour ce faire j'ai développé un callfixer sous la forme d'une dll qui sera injectée dans le processus à l'aide de mon plugin IinjOlly. Cette DLL fonctionne grossièrement de cette façon :

  1. Recherche de 2 signatures donnant les 2 adresses auxquelles l'adresse de l'API apparait dans eax lors de la redirection
  2. Pose de 2 HBPs sur ces adresses à l'aide des APIs [Get/Set]ThreadContext (Asprotect fais des CRCs sur son propre code pour déchiffrer le noms des APIs, ils faut donc éviter de modifier son code ;) )
  3. Mise en place d'un VectoredExceptionHandler afin de récupérer les exceptions générées par nos HBPs avant les SEH
  4. Recherche des call Asprotect, saut sur ces calls
  5. Logiquement un de nos HBPs devrait intercepter l'exécution de la routine de redirection et nous donner l'adresse de l'API correspondante
  6. Recherche de l'API dans l'IAT, patch de l'instruction

Au final cela nous donne ca :

  1. #include <windows.h>
  2.  
  3. #define AsproSign "\x8B\xF8\x8B\x45\xF4\x03\xB8\xE0\x00\x00\x00\x8B\x45\xEC\x03\xC7\x03\x45\xD8\x89\x45\xEC\x8B\x45\xE8\x2B\xC7\x2B\x45\xD8\x89\x45\xE8\x33\xC0\x8A\x43\x01\x8D\x04\x40\x8B\x55\xF4\x8B\x54\x82\x68\x8B\x06"
  4. #define AsproSign2 "\xC6\x00\xCF\x8D\x45\x0C\x50\x8D\x45\xCC\x50\x8D\x45\xDC\x50\x8B\x45\xFC\x50\x8B\x45\xF8\x50\x8B\x45\x10\x50\x8B\x45\xF4\x50"
  5.  
  6. #define debut_scan (PBYTE)0x0401000
  7. #define fin_scan (PBYTE)0x00407FD0
  8.  
  9. #define debut_iat (PDWORD)0x00408000
  10. #define fin_iat (PDWORD)0x000408114
  11.  
  12. #define FunCallSign1 0x438B1C75
  13. #define FunCallSign2 0xFF5309EB
  14.  
  15. #define GetProcSign1 0x8B0C558B
  16. #define GetProcSign2 0x053B0845
  17.  
  18. #define AddrCallRedir 0x04011EA
  19.  
  20. PBYTE Redirection;
  21. PBYTE AddrAPI;
  22. PBYTE AddrAPI2;
  23.  
  24. DWORD originalEsp;
  25.  
  26. PBYTE SearchSign(unsigned char *Sign,DWORD SignSize)
  27. {
  28. MEMORY_BASIC_INFORMATION mbi;
  29. DWORD i,j,k;
  30. PBYTE Mem;
  31.  
  32. mbi.BaseAddress = 0;
  33.  
  34. while (VirtualQuery(mbi.BaseAddress,&mbi,sizeof(MEMORY_BASIC_INFORMATION)))
  35. {
  36. if ((mbi.State == MEM_COMMIT) && ((mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY | PAGE_READONLY)) != 0))
  37. {
  38. Mem = (PBYTE)mbi.BaseAddress;
  39. for (i=0;i <= mbi.RegionSize - SignSize ; i++)
  40. {
  41. k = 0;
  42. if (&Mem[i] != Sign)
  43. {
  44. for (j=0;j<SignSize;j++)
  45. if (Mem[i+j] != Sign[j])
  46. break;
  47. else k++;
  48. if (k==SignSize)
  49. return &Mem[i];
  50. }
  51. }
  52. }
  53. mbi.BaseAddress = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
  54. }
  55. return 0;
  56. }
  57.  
  58. DWORD __declspec(naked) getApiAddress(DWORD address)
  59. {
  60. __asm {
  61. pushad
  62. pushfd
  63. push FS:[0]
  64. mov originalEsp , esp
  65. jmp DWORD ptr [esp+0x2C]
  66. }
  67. }
  68.  
  69.  
  70.  
  71. DWORD __declspec(naked) retour(DWORD address, PDWORD Destination , PDWORD JccType)
  72. {
  73. __asm {
  74. mov esp , originalEsp
  75. mov DWORD ptr [esp + 0x1C + 4 + 4] , eax ; eax = valeur de retour du handler ...
  76. pop dword ptr FS:[0]
  77. popfd
  78. popad
  79. retn
  80. }
  81. }
  82.  
  83. LONG CALLBACK ExcHandler(PEXCEPTION_POINTERS ExceptionInfo)
  84. {
  85. if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
  86. {
  87. if ((ExceptionInfo->ExceptionRecord->ExceptionAddress == AddrAPI) ||
  88. (ExceptionInfo->ExceptionRecord->ExceptionAddress == AddrAPI2))
  89. {
  90. ExceptionInfo->ContextRecord->Eip = (DWORD)retour;
  91. ExceptionInfo->ContextRecord->Dr0 = (DWORD)AddrAPI;
  92. ExceptionInfo->ContextRecord->Dr7 = DR7flag(OneByteLength,BreakOnExec,LocalFlag,0);
  93. ExceptionInfo->ContextRecord->Dr1 = (DWORD)AddrAPI2;
  94. ExceptionInfo->ContextRecord->Dr7 |= DR7flag(OneByteLength,BreakOnExec,LocalFlag,1);
  95. return EXCEPTION_CONTINUE_EXECUTION;
  96. }
  97. else
  98. return EXCEPTION_CONTINUE_SEARCH;
  99. }
  100. else
  101. {
  102. return EXCEPTION_CONTINUE_SEARCH;
  103. }
  104. }
  105.  
  106. void SetHBPonExec(DWORD address,int num)
  107. {
  108. CONTEXT c;
  109.  
  110. c.ContextFlags = CONTEXT_DEBUG_REGISTERS;
  111. GetThreadContext(GetCurrentThread(),&c);
  112. *(PDWORD)((int)(&c.Dr0) + sizeof(DWORD)*num) = address;
  113. c.Dr7 |= DR7flag(OneByteLength,BreakOnExec,LocalFlag,num);
  114. SetThreadContext(GetCurrentThread(),&c);
  115. }
  116.  
  117. void go(HINSTANCE hinstDLL)
  118. {
  119. PBYTE curseur;
  120. PDWORD IAT;
  121. Redirection = *((PBYTE*)(AddrCallRedir + 1)) + AddrCallRedir + 5;
  122. DebugBreak();
  123.  
  124. AddVectoredExceptionHandler(0,ExcHandler);
  125. AddrAPI = SearchSign(AsproSign,sizeof(AsproSign)-1);
  126. AddrAPI2 = SearchSign(AsproSign2,sizeof(AsproSign2)-1);
  127. if ((! AddrAPI) || (! AddrAPI2))
  128. {
  129. DebugBreak();
  130. return;
  131. }
  132. AddrAPI += 0x5B;
  133. AddrAPI2 += 0x12;
  134. SetHBPonExec((DWORD)AddrAPI,0);
  135. SetHBPonExec((DWORD)AddrAPI2,1);
  136.  
  137. for (curseur = debut_scan ; curseur <= fin_scan ; curseur ++)
  138. {
  139. if (*curseur == 0xE8)
  140. {
  141. PBYTE destination = *((PDWORD)(curseur + 1)) + curseur + 5;
  142. if (destination == Redirection)
  143. {
  144. DWORD addr = getApiAddress((DWORD)curseur);
  145. BOOL fixed = FALSE;
  146. for (IAT = debut_iat ; IAT <= fin_iat ; IAT ++)
  147. {
  148. if (*IAT == addr)
  149. {
  150. *((PWORD)curseur) = 0x15FF;
  151. *((PDWORD*)(curseur + 2)) = IAT;
  152. fixed = TRUE;
  153. break;
  154. }
  155. }
  156. if (! fixed)
  157. DebugBreak();
  158. }
  159. }
  160. }
  161. }
  162.  
  163. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
  164. {
  165. switch (fdwReason)
  166. {
  167. case DLL_PROCESS_ATTACH:
  168. go(hinstDLL);
  169. break;
  170. case DLL_PROCESS_DETACH:
  171.  
  172. break;
  173. case DLL_THREAD_ATTACH:
  174.  
  175. break;
  176. case DLL_THREAD_DETACH:
  177.  
  178. break;
  179. }
  180. return TRUE;
  181. }

Dans un soucis de clarté (apparemment ca s'écrit comme ca ...) j'ai supprimé pas mal de lignes qui sont spécifiques à certains cas qui ne sont pas présent dans notre programme donc n'espérez pas unpacker n'importe quel programme protégé par asprotect à l'aide de ce code (néanmoins vous aurez déjà une bonne base ;))

Il nous suffit maintenant de dumper notre programme avec LordPe et de reconstruire les imports avec ImpRec, là je ne vous ferai pas de dessins, le net regorge de tutos sur l'utilisation de ces outils.

Tous ca pour ca ?!

Maintenant que nous avons notre exécutable unpacké, nous allons pouvoir l'étudier sérieusement et voir pourquoi cette étape était finalement complètement facultative.

En effet après une rapide analyse du code on se rend compte que cet exécutable se contente d'extraire de ses ressources un autre exécutable et de le lancer. L'exécutable en question n'est même pas chiffré dans les ressources ...

Il est extrait dans un fichier caché nommé %s%s.exe (ce qui fait crasher ollydbg) puis lancé en utilisant CreateProcessA. Il suffisait donc pour ceux chez qui le challenge se lançait de copier %s%s.exe ce qui leur économisait pas mal de temps ;) (chez moi le crack-me ne fonctionne pas, CreateProcessA refusant apparemment de lancer un fichier dont le nom contient des % et renvoie une erreur ERROR_BAD_EXE_FORMAT (000000C1))

Une fois %s%s.exe terminé, le 1er binaire supprime le fichier caché et se termine lui aussi.

Table Table Table je vous aime

Nous passons donc à l'étude de %s%s.exe (que nous nous empressons de renommer pour pouvoir l'ouvrir sous olly).

Arrivé à l'entry point nous reconnaissons encore clairement le début d'un programme compilé avec VC :

  1. 004018F4 > $ E8 0BD70000 CALL 0040F004
  2. 004018F9 .^ E9 78FEFFFF JMP 00401776
  3. 004018FE > 8BFF MOV EDI,EDI
  4. [...]

à ceci près que le call pointe sur une fonction qui ne ressemble en rien à celle appelée normalement, on se rend compte aussi que cette fonction est appelée un peu partout dans le code :

  1. 0040F004 /$ 50 PUSH EAX
  2. 0040F005 |. 52 PUSH EDX
  3. 0040F006 |. E8 00000000 CALL 0040F00B
  4. 0040F00B |$ 58 POP EAX
  5. 0040F00C |. 05 3A000000 ADD EAX,3A
  6. 0040F011 |> 8B10 /MOV EDX,[EAX]
  7. 0040F013 |. 395424 08 |CMP [ESP+8],EDX
  8. 0040F017 |. 74 0B |JE SHORT 0040F024
  9. 0040F019 |. 05 08000000 |ADD EAX,8
  10. 0040F01E |. 85D2 |TEST EDX,EDX
  11. 0040F020 |. 74 0A |JE SHORT 0040F02C
  12. 0040F022 |.^ EB ED \JMP SHORT 0040F011
  13. 0040F024 |> 5A POP EDX
  14. 0040F025 |. 8B40 04 MOV EAX,[EAX+4]
  15. 0040F028 |. 870424 XCHG [ESP],EAX
  16. 0040F02B |. C3 RETN
  17. 0040F02C |> 8B10 /MOV EDX,[EAX]
  18. 0040F02E |. 395424 08 |CMP [ESP+8],EDX
  19. 0040F032 |. 74 07 |JE SHORT 0040F03B
  20. 0040F034 |. 05 08000000 |ADD EAX,8
  21. 0040F039 |.^ EB F1 \JMP SHORT 0040F02C
  22. 0040F03B |> 5A POP EDX
  23. 0040F03C |. 8B40 04 MOV EAX,[EAX+4]
  24. 0040F03F |. 8B00 MOV EAX,[EAX]
  25. 0040F041 |. 870424 XCHG [ESP],EAX
  26. 0040F044 \. C3 RETN

Après une rapide étude, on se rend vite compte que cette fonction redirige l'exécution vers la fonction d'origine à partir de l'adresse de retour, il suffit donc d'écrire un petit code qui se chargera de remplacer tout les call redirection par les calls originaux. Pour se faire (et pour changer ;) ) j'ai écrit une petite dll qui une fois injectée scann l'exécutable à la recherche de call redirection et de call [ptr redirection puis fixe chacun des calls, le code étant relativement peu volumineux je vous le met ici :

  1. #include "main.h"
  2.  
  3. #define debut_scan (PBYTE)0x0401000
  4. #define fin_scan (PBYTE)0x0415FFF
  5. #define Redirection (PBYTE)0x0040F004
  6. #define table ((PDWORD)0x040F045)
  7. #define table2 ((PDWORD)0x004101A5)
  8.  
  9. DWORD getfunaddress(PBYTE address)
  10. {
  11. int i;
  12.  
  13. for (i = 0; table[i] ; i+=2)
  14. if (table[i] == (DWORD)address+5)
  15. return table[i+1];
  16.  
  17. DebugBreak();
  18. return 0;
  19. }
  20.  
  21. DWORD getapiaddress(PBYTE address)
  22. {
  23. int i;
  24.  
  25. for (i = 0; table2[i] ; i+=2)
  26. if (table2[i] == (DWORD)address+6)
  27. return table2[i+1];
  28.  
  29. DebugBreak();
  30. return 0;
  31. }
  32.  
  33.  
  34. void DLL_EXPORT pouet()
  35. {
  36. PBYTE curseur;
  37. DebugBreak();
  38. for (curseur = debut_scan ; curseur <= fin_scan ; curseur ++)
  39. {
  40. if (*curseur == 0xE8)
  41. {
  42. PBYTE destination = *((PDWORD)(curseur + 1)) + curseur + 5;
  43. if (destination == Redirection)
  44. *((PDWORD)(curseur + 1)) = getfunaddress(curseur) - (DWORD)curseur - 5;
  45. }
  46. else if ((*(PWORD)curseur == 0x15FF) && (*(PDWORD)(curseur + 2) == 0x40F000))
  47. {
  48. *((PDWORD)(curseur + 2)) = getapiaddress(curseur);
  49. }
  50. }
  51. }

Je te vois petit sérial !

Une fois la dll injectée, l'exe modifié dumpé, nous pouvons passer à la vérification du sérial en lui même (enfin diront certains ...). On ouvre donc notre tout nouvel exe et on trouve rapidement que :

  • l'exécutable affiche une jolie dialog box dont la dialog proc se trouve en 00401620
  • que le sérial est vérifié en 004011F0 dès qu'un caractère est entré / modifié
  • que la pression du bouton valider ne fait que regarder la valeur d'un byte en 40C6A0 pour afficher ou non le message de réussite.

Passons donc maintenant à l'étude de la fonction de vérification (en 004011F0 donc) plusieurs petites suprises nous attendent :

  1. double RDTSC, les valeurs de eax et ecx sont stockées et serviront surement plus tard ....
  2. vérification du nom de l'exécutable à l'aide de GetModuleFileNameW, remplacement d'un test eax , eax par un xor eax , eax
  3. exceptions (single step) elles servent juste de saut et sont donc facilement patchables ...
  4. CRC du code de vérification

Nous avons donc maintenant un exécutable parfaitement clean que l'on peut étudier avec IDA etc.

Au final on se rends compte que la vérification est juste un simple enchainement de vérifications d'équations de plusieurs variables (qui sont données par découpage du sérial en mots de 4 chars convertis en words ("01234567" => 0123, 4567) ) et qui doivent vérifier :

serial : h1 a b c d e f g h2
h1::h2 = hash(serial[4:32])
e = 4919
g-d % b = c
f + 9838 = a
62336 - a = b
g - 4919 == f
f + g - 4919 = d
g premier
g composé de 5 7 et 8
g % 3 = 1
g % 99 = 64

Il suffit donc maintenant d'écrire un petit programme qui va se charger de retrouver ce sérial, le voici pour vous :

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. unsigned char hexData[1032] = {
  5. 0x00, 0x00, 0x00, 0x00, 0x96, 0x30, 0x07, 0x77,
  6. 0x2C, 0x61, 0x0E, 0xEE, 0xBA, 0x51, 0x09, 0x99,
  7. 0x19, 0xC4, 0x6D, 0x07, 0x8F, 0xF4, 0x6A, 0x70,
  8. 0x35, 0xA5, 0x63, 0xE9, 0xA3, 0x95, 0x64, 0x9E,
  9. 0x32, 0x88, 0xDB, 0x0E, 0xA4, 0xB8, 0xDC, 0x79,
  10. 0x1E, 0xE9, 0xD5, 0xE0, 0x88, 0xD9, 0xD2, 0x97,
  11. 0x2B, 0x4C, 0xB6, 0x09, 0xBD, 0x7C, 0xB1, 0x7E,
  12. 0x07, 0x2D, 0xB8, 0xE7, 0x91, 0x1D, 0xBF, 0x90,
  13. 0x64, 0x10, 0xB7, 0x1D, 0xF2, 0x20, 0xB0, 0x6A,
  14. 0x48, 0x71, 0xB9, 0xF3, 0xDE, 0x41, 0xBE, 0x84,
  15. 0x7D, 0xD4, 0xDA, 0x1A, 0xEB, 0xE4, 0xDD, 0x6D,
  16. 0x51, 0xB5, 0xD4, 0xF4, 0xC7, 0x85, 0xD3, 0x83,
  17. 0x56, 0x98, 0x6C, 0x13, 0xC0, 0xA8, 0x6B, 0x64,
  18. 0x7A, 0xF9, 0x62, 0xFD, 0xEC, 0xC9, 0x65, 0x8A,
  19. 0x4F, 0x5C, 0x01, 0x14, 0xD9, 0x6C, 0x06, 0x63,
  20. 0x63, 0x3D, 0x0F, 0xFA, 0xF5, 0x0D, 0x08, 0x8D,
  21. 0xC8, 0x20, 0x6E, 0x3B, 0x5E, 0x10, 0x69, 0x4C,
  22. 0xE4, 0x41, 0x60, 0xD5, 0x72, 0x71, 0x67, 0xA2,
  23. 0xD1, 0xE4, 0x03, 0x3C, 0x47, 0xD4, 0x04, 0x4B,
  24. 0xFD, 0x85, 0x0D, 0xD2, 0x6B, 0xB5, 0x0A, 0xA5,
  25. 0xFA, 0xA8, 0xB5, 0x35, 0x6C, 0x98, 0xB2, 0x42,
  26. 0xD6, 0xC9, 0xBB, 0xDB, 0x40, 0xF9, 0xBC, 0xAC,
  27. 0xE3, 0x6C, 0xD8, 0x32, 0x75, 0x5C, 0xDF, 0x45,
  28. 0xCF, 0x0D, 0xD6, 0xDC, 0x59, 0x3D, 0xD1, 0xAB,
  29. 0xAC, 0x30, 0xD9, 0x26, 0x3A, 0x00, 0xDE, 0x51,
  30. 0x80, 0x51, 0xD7, 0xC8, 0x16, 0x61, 0xD0, 0xBF,
  31. 0xB5, 0xF4, 0xB4, 0x21, 0x23, 0xC4, 0xB3, 0x56,
  32. 0x99, 0x95, 0xBA, 0xCF, 0x0F, 0xA5, 0xBD, 0xB8,
  33. 0x9E, 0xB8, 0x02, 0x28, 0x08, 0x88, 0x05, 0x5F,
  34. 0xB2, 0xD9, 0x0C, 0xC6, 0x24, 0xE9, 0x0B, 0xB1,
  35. 0x87, 0x7C, 0x6F, 0x2F, 0x11, 0x4C, 0x68, 0x58,
  36. 0xAB, 0x1D, 0x61, 0xC1, 0x3D, 0x2D, 0x66, 0xB6,
  37. 0x90, 0x41, 0xDC, 0x76, 0x06, 0x71, 0xDB, 0x01,
  38. 0xBC, 0x20, 0xD2, 0x98, 0x2A, 0x10, 0xD5, 0xEF,
  39. 0x89, 0x85, 0xB1, 0x71, 0x1F, 0xB5, 0xB6, 0x06,
  40. 0xA5, 0xE4, 0xBF, 0x9F, 0x33, 0xD4, 0xB8, 0xE8,
  41. 0xA2, 0xC9, 0x07, 0x78, 0x34, 0xF9, 0x00, 0x0F,
  42. 0x8E, 0xA8, 0x09, 0x96, 0x18, 0x98, 0x0E, 0xE1,
  43. 0xBB, 0x0D, 0x6A, 0x7F, 0x2D, 0x3D, 0x6D, 0x08,
  44. 0x97, 0x6C, 0x64, 0x91, 0x01, 0x5C, 0x63, 0xE6,
  45. 0xF4, 0x51, 0x6B, 0x6B, 0x62, 0x61, 0x6C, 0x1C,
  46. 0xD8, 0x30, 0x65, 0x85, 0x4E, 0x00, 0x62, 0xF2,
  47. 0xED, 0x95, 0x06, 0x6C, 0x7B, 0xA5, 0x01, 0x1B,
  48. 0xC1, 0xF4, 0x08, 0x82, 0x57, 0xC4, 0x0F, 0xF5,
  49. 0xC6, 0xD9, 0xB0, 0x65, 0x50, 0xE9, 0xB7, 0x12,
  50. 0xEA, 0xB8, 0xBE, 0x8B, 0x7C, 0x88, 0xB9, 0xFC,
  51. 0xDF, 0x1D, 0xDD, 0x62, 0x49, 0x2D, 0xDA, 0x15,
  52. 0xF3, 0x7C, 0xD3, 0x8C, 0x65, 0x4C, 0xD4, 0xFB,
  53. 0x58, 0x61, 0xB2, 0x4D, 0xCE, 0x51, 0xB5, 0x3A,
  54. 0x74, 0x00, 0xBC, 0xA3, 0xE2, 0x30, 0xBB, 0xD4,
  55. 0x41, 0xA5, 0xDF, 0x4A, 0xD7, 0x95, 0xD8, 0x3D,
  56. 0x6D, 0xC4, 0xD1, 0xA4, 0xFB, 0xF4, 0xD6, 0xD3,
  57. 0x6A, 0xE9, 0x69, 0x43, 0xFC, 0xD9, 0x6E, 0x34,
  58. 0x46, 0x88, 0x67, 0xAD, 0xD0, 0xB8, 0x60, 0xDA,
  59. 0x73, 0x2D, 0x04, 0x44, 0xE5, 0x1D, 0x03, 0x33,
  60. 0x5F, 0x4C, 0x0A, 0xAA, 0xC9, 0x7C, 0x0D, 0xDD,
  61. 0x3C, 0x71, 0x05, 0x50, 0xAA, 0x41, 0x02, 0x27,
  62. 0x10, 0x10, 0x0B, 0xBE, 0x86, 0x20, 0x0C, 0xC9,
  63. 0x25, 0xB5, 0x68, 0x57, 0xB3, 0x85, 0x6F, 0x20,
  64. 0x09, 0xD4, 0x66, 0xB9, 0x9F, 0xE4, 0x61, 0xCE,
  65. 0x0E, 0xF9, 0xDE, 0x5E, 0x98, 0xC9, 0xD9, 0x29,
  66. 0x22, 0x98, 0xD0, 0xB0, 0xB4, 0xA8, 0xD7, 0xC7,
  67. 0x17, 0x3D, 0xB3, 0x59, 0x81, 0x0D, 0xB4, 0x2E,
  68. 0x3B, 0x5C, 0xBD, 0xB7, 0xAD, 0x6C, 0xBA, 0xC0,
  69. 0x20, 0x83, 0xB8, 0xED, 0xB6, 0xB3, 0xBF, 0x9A,
  70. 0x0C, 0xE2, 0xB6, 0x03, 0x9A, 0xD2, 0xB1, 0x74,
  71. 0x39, 0x47, 0xD5, 0xEA, 0xAF, 0x77, 0xD2, 0x9D,
  72. 0x15, 0x26, 0xDB, 0x04, 0x83, 0x16, 0xDC, 0x73,
  73. 0x12, 0x0B, 0x63, 0xE3, 0x84, 0x3B, 0x64, 0x94,
  74. 0x3E, 0x6A, 0x6D, 0x0D, 0xA8, 0x5A, 0x6A, 0x7A,
  75. 0x0B, 0xCF, 0x0E, 0xE4, 0x9D, 0xFF, 0x09, 0x93,
  76. 0x27, 0xAE, 0x00, 0x0A, 0xB1, 0x9E, 0x07, 0x7D,
  77. 0x44, 0x93, 0x0F, 0xF0, 0xD2, 0xA3, 0x08, 0x87,
  78. 0x68, 0xF2, 0x01, 0x1E, 0xFE, 0xC2, 0x06, 0x69,
  79. 0x5D, 0x57, 0x62, 0xF7, 0xCB, 0x67, 0x65, 0x80,
  80. 0x71, 0x36, 0x6C, 0x19, 0xE7, 0x06, 0x6B, 0x6E,
  81. 0x76, 0x1B, 0xD4, 0xFE, 0xE0, 0x2B, 0xD3, 0x89,
  82. 0x5A, 0x7A, 0xDA, 0x10, 0xCC, 0x4A, 0xDD, 0x67,
  83. 0x6F, 0xDF, 0xB9, 0xF9, 0xF9, 0xEF, 0xBE, 0x8E,
  84. 0x43, 0xBE, 0xB7, 0x17, 0xD5, 0x8E, 0xB0, 0x60,
  85. 0xE8, 0xA3, 0xD6, 0xD6, 0x7E, 0x93, 0xD1, 0xA1,
  86. 0xC4, 0xC2, 0xD8, 0x38, 0x52, 0xF2, 0xDF, 0x4F,
  87. 0xF1, 0x67, 0xBB, 0xD1, 0x67, 0x57, 0xBC, 0xA6,
  88. 0xDD, 0x06, 0xB5, 0x3F, 0x4B, 0x36, 0xB2, 0x48,
  89. 0xDA, 0x2B, 0x0D, 0xD8, 0x4C, 0x1B, 0x0A, 0xAF,
  90. 0xF6, 0x4A, 0x03, 0x36, 0x60, 0x7A, 0x04, 0x41,
  91. 0xC3, 0xEF, 0x60, 0xDF, 0x55, 0xDF, 0x67, 0xA8,
  92. 0xEF, 0x8E, 0x6E, 0x31, 0x79, 0xBE, 0x69, 0x46,
  93. 0x8C, 0xB3, 0x61, 0xCB, 0x1A, 0x83, 0x66, 0xBC,
  94. 0xA0, 0xD2, 0x6F, 0x25, 0x36, 0xE2, 0x68, 0x52,
  95. 0x95, 0x77, 0x0C, 0xCC, 0x03, 0x47, 0x0B, 0xBB,
  96. 0xB9, 0x16, 0x02, 0x22, 0x2F, 0x26, 0x05, 0x55,
  97. 0xBE, 0x3B, 0xBA, 0xC5, 0x28, 0x0B, 0xBD, 0xB2,
  98. 0x92, 0x5A, 0xB4, 0x2B, 0x04, 0x6A, 0xB3, 0x5C,
  99. 0xA7, 0xFF, 0xD7, 0xC2, 0x31, 0xCF, 0xD0, 0xB5,
  100. 0x8B, 0x9E, 0xD9, 0x2C, 0x1D, 0xAE, 0xDE, 0x5B,
  101. 0xB0, 0xC2, 0x64, 0x9B, 0x26, 0xF2, 0x63, 0xEC,
  102. 0x9C, 0xA3, 0x6A, 0x75, 0x0A, 0x93, 0x6D, 0x02,
  103. 0xA9, 0x06, 0x09, 0x9C, 0x3F, 0x36, 0x0E, 0xEB,
  104. 0x85, 0x67, 0x07, 0x72, 0x13, 0x57, 0x00, 0x05,
  105. 0x82, 0x4A, 0xBF, 0x95, 0x14, 0x7A, 0xB8, 0xE2,
  106. 0xAE, 0x2B, 0xB1, 0x7B, 0x38, 0x1B, 0xB6, 0x0C,
  107. 0x9B, 0x8E, 0xD2, 0x92, 0x0D, 0xBE, 0xD5, 0xE5,
  108. 0xB7, 0xEF, 0xDC, 0x7C, 0x21, 0xDF, 0xDB, 0x0B,
  109. 0xD4, 0xD2, 0xD3, 0x86, 0x42, 0xE2, 0xD4, 0xF1,
  110. 0xF8, 0xB3, 0xDD, 0x68, 0x6E, 0x83, 0xDA, 0x1F,
  111. 0xCD, 0x16, 0xBE, 0x81, 0x5B, 0x26, 0xB9, 0xF6,
  112. 0xE1, 0x77, 0xB0, 0x6F, 0x77, 0x47, 0xB7, 0x18,
  113. 0xE6, 0x5A, 0x08, 0x88, 0x70, 0x6A, 0x0F, 0xFF,
  114. 0xCA, 0x3B, 0x06, 0x66, 0x5C, 0x0B, 0x01, 0x11,
  115. 0xFF, 0x9E, 0x65, 0x8F, 0x69, 0xAE, 0x62, 0xF8,
  116. 0xD3, 0xFF, 0x6B, 0x61, 0x45, 0xCF, 0x6C, 0x16,
  117. 0x78, 0xE2, 0x0A, 0xA0, 0xEE, 0xD2, 0x0D, 0xD7,
  118. 0x54, 0x83, 0x04, 0x4E, 0xC2, 0xB3, 0x03, 0x39,
  119. 0x61, 0x26, 0x67, 0xA7, 0xF7, 0x16, 0x60, 0xD0,
  120. 0x4D, 0x47, 0x69, 0x49, 0xDB, 0x77, 0x6E, 0x3E,
  121. 0x4A, 0x6A, 0xD1, 0xAE, 0xDC, 0x5A, 0xD6, 0xD9,
  122. 0x66, 0x0B, 0xDF, 0x40, 0xF0, 0x3B, 0xD8, 0x37,
  123. 0x53, 0xAE, 0xBC, 0xA9, 0xC5, 0x9E, 0xBB, 0xDE,
  124. 0x7F, 0xCF, 0xB2, 0x47, 0xE9, 0xFF, 0xB5, 0x30,
  125. 0x1C, 0xF2, 0xBD, 0xBD, 0x8A, 0xC2, 0xBA, 0xCA,
  126. 0x30, 0x93, 0xB3, 0x53, 0xA6, 0xA3, 0xB4, 0x24,
  127. 0x05, 0x36, 0xD0, 0xBA, 0x93, 0x06, 0xD7, 0xCD,
  128. 0x29, 0x57, 0xDE, 0x54, 0xBF, 0x67, 0xD9, 0x23,
  129. 0x2E, 0x7A, 0x66, 0xB3, 0xB8, 0x4A, 0x61, 0xC4,
  130. 0x02, 0x1B, 0x68, 0x5D, 0x94, 0x2B, 0x6F, 0x2A,
  131. 0x37, 0xBE, 0x0B, 0xB4, 0xA1, 0x8E, 0x0C, 0xC3,
  132. 0x1B, 0xDF, 0x05, 0x5A, 0x8D, 0xEF, 0x02, 0x2D,
  133. 0x3C, 0x75, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E
  134. };
  135.  
  136.  
  137. int isPrime(int a)
  138. {
  139. int i;
  140. if (!(a & 1))
  141. return 0;
  142. for(i = 3; i*i < a ; i++)
  143. if (!(a % i))
  144. return 0;
  145. return 1;
  146. }
  147.  
  148. int isGoodComposed(int a)
  149. {
  150. int i;
  151. static const char tab[] = {0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0};
  152.  
  153. for (i = 0; i < 4 ; i++)
  154. if (! tab[(a >> (i << 2)) & 0xF])
  155. return 0;
  156. return 1;
  157. }
  158.  
  159. unsigned int Hash(char *a1, int a2)
  160. {
  161. unsigned int i;
  162. char *v3;
  163. int v4;
  164.  
  165. v4 = a2;
  166. v3 = a1;
  167. for ( i = 0xFFFFFFFF; v4; ++v3 )
  168. {
  169. i = ((int*)hexData)[(unsigned char)((char)i ^ *v3)] ^ (i >> 8);
  170. --v4;
  171. }
  172. return ~i;
  173. }
  174.  
  175.  
  176. int main()
  177. {
  178. int a , b ,c ,d , e ,f , g;
  179. int hash;
  180. char serial[39];
  181.  
  182. for (g = 64 ;(g < 0xFFFF) && ((g % 3 != 1) || (! isPrime(g)) || (! isGoodComposed(g))) ; g += 99);
  183. if (g >= 0xFFFF)
  184. {
  185. printf("fail @ g !\n");
  186. return -1;
  187. }
  188. //printf("g : %04X\n",g);
  189. e = 4919;
  190. f = g - e;
  191. d = f + g - e;
  192. a = f + 2*e;
  193. b = 62336 - a;
  194. c = g-d % b;
  195. sprintf(serial,"%04X%04X%04X%04X%04X%04X%04X",a,b,c,d,e,f,g);
  196. hash = Hash(serial,7*4);
  197. printf("serial : %04X%04X%04X%04X%04X%04X%04X%04X%04X\n",(hash >> 16),a,b,c,d,e,f,g,hash & 0xFFFF);
  198. }

On le lance et ... BOUM on a notre sérial : 20518BBC67C415ADCA9C1337654E7885A02C

Voila voila !

Je me rends compte que la fin de mon post est un peu rapide mais bon, j'ai déjà bien écris et j'ai un peu la flemme d'expliquer la vérification du sérial qui au final n'avait que peu d'interet ... Néanmoins si vous avez des questions précises ou si vous désirez un des binaires du challenge modifié, n'hésitez pas ;)