Dans cet article je vais essayer d'expliquer comment contourner une protection à l'aide d'un désassembleur. Il n'a pas pour but d'apprendre à cracker mais pour montrer la faiblesse de certaine protection par mot de passe dans les programmes. Le but étant de vous sensibiliser afin de repenser votre façon de coder si elle présente les mêmes failles que le programme qui sera traité dans l'exemple. Je tiens à mettre les choses au clair au cas où certain hackers viennent sur ce site, je ne prétend pas maitriser le reversing, je me contente simplement de sensibiliser les jeunes programmeurs à faire attention.





0x01. DEFINITIONS


Je met permet de définir en bref ce terme pour éviter toutes confusions. Le reversing est l'art de décortiquer un programme. A l'origine il est question de reverser un programme lorsqu'il a un défaut de conception et que l'on a pas accès aux sources. Il permettait donc pour les experts de rendre inoffensif un virus ou alors d'empêcher à un programme trop curieux de surveiller son ordinateur à son insu.

Tout programme fait un appel à une fonction interne ou externe, c'est cette fonction qui permet le bon fonctionnement du programme. Si on arrive à conna�tre le rôle de ces fonctions on peut alors par exemple inverser les tests, les esquiver ou les supprimer afin que le programme fasse ce qu'on lui demande plutôt que de remplir sa fonction d'origine. Définissons également quelques notions qui sont omniprésentes.





0x02. STRING DATA


Les données de chaines de caractères, appelées 'String Data' représentent les ressources du programme, c'est à dire le nom des menus, des boites de dialogues et des titres. Par exemple le bloc notes doit avoir comme 'String Data' la ressource "Fichier" car elle fait référence à un menu.





0x03. FONCTIONS EXPORTEES


Une fonction est un bout de programme qui correspond en assembleur souvent à un saut. Toutes les fonctions exportées ne correspondent pas nécessairement à un saut, et c'est même rarement le cas, car leur utilité est de transmettre une fonction du programme vers un autre, ou vers une DLL. La fonction d'achat d'un programme payant, enverra des informations de versions et autres relatives à un autre programme (register.exe, par exemple) Il y a aura donc une fonction exportée correspondante.





0x04. FONCTIONS IMPORTEES


Les fonctions importées correspondent en grande partie aux fonctions des DLLs. Il est très courant de voir un programme appeler des fonctions de Windows, ne serait-ce que pour l'affichage. Ce qu'on cherche dans les fonctions importées c'est souvent les appels à la base de registre, oà au boite de Dialogue (comme MessageBox, ...) . Voire à la fonction ExitProcess de Kernel32.dll. C'est à partir de ça que parfois il faut se baser pour 'cracker' un programme.





0x05. WINDASM


Inutile de présenter cet incontournable outil de désassemblage. Pour accéder directement à Windasm, cliquez simplement sur le titre. Accouplé à OllyDbg et d'une bonne logique, ils forment de très bons outils complémentaires. Dans l'exemple de cet article nous allons simplement montrer la puissance du deadlisting, c'est à dire une approche de reversing par désassemblage plutot que par débogage. Nous allons donc commencer par lancer Windasm et ouvrir le programme à cracker communément appelé 'crackme'. Une fois le fichier ouvert :


Refs -> String Data References 

Une boite de dialogue appara�tra, il faudra alors chercher les valeurs sensibles...


files/langage_c/reversing/list_string.jpg

Après avoir trouvé ces informations, Windasm nous envoie au code source assembleur faisant référence à cette chaine de caractère.

Nous avons dans ce cas plein d'informations intéressantes, notamment l'endroit oà va la fonction de comparaison lorsque le test est validé ici :


:004011128        cmp byte ptr  [ebp-04] , 61
:00401112C        je 0040114D
:00401112E        cmp byte ptr  [ebp-04] , 7A
:004011132        je 0040114D
:004011134        cmp byte ptr  [ebp-04] , 65
:004011138        je 0040114D
:00401113A        cmp byte ptr  [ebp-04] , 72
:00401113E        je 0040114D
...
Possible StringData Ref from Data Obj -> "gagne :)"
:00401114D        push 0040C10A 

Ici on voit très nettement que si le test est validé alors il va à l'adresse correspondante à celle de la chaine de caractère "gagne :)"


files/langage_c/reversing/references.gif

Pour cracker ce programme il y a donc plusieurs méthodes :

[1] dire au programme de faire le test inverse changer les je en jne
[2] comme on voit que le programme continue en dessous de son test pour dire que le mot de passe n'est pas bon, il suffit de lui dire de ne rien faire, ni tester ni dire que le mot de passe n'est pas bon
[3] deviner le mot de passe
[4] ou bien de lui dire de faire un test toujours vrai pour qu'il saute forcément à la bonne adresse

Voici comment faire pour chacun de ces cas.





0x06. REVERSING



[1] Inversion de test

Pour ça il s'agit de transformer un JE en JNE, de manière plus générale de transformer un test commençant par J en un test commençant par JN Pour inverser le test il faut, souvent mais pas tout le temps, de mettre le bit de poids faible du premier octet en partant de la droite à 1. Voici une liste de saut conditionnels et inconditionnels.


; Sauts conditionnels et opérande
; ------------------------------- 
JA      Jump if Above                   =JNBE   ;  op2 > op1
JAE     Jump if Above or Equal          =JNB    ;  op2 >= op1
JB      Jump if Below                   =JNAE   ;  op2 < op1
JBE     Jump if Below or Equal          =JNA    ;  op2 <= op1
JCXZ    Jump if CX = 0                          ;  CX = 0
JG      Jump if Greaater                =JNLE   ;  op2 > op1
JGE     Jump if Greater or Equal        =JNL    ;  op2 >= op1
JL      Jump if Less                    =JNGE   ;  op2 < op1
JLE     Jump if Less or Equal                   ;  op2 <= op1
JNA     Jump if Not Above               =JBE    ;  op2 <= op1
JNAE    jump if Not Abobe or Equal              ;  op2 < op1
JNE     Jump if Not Equal               =JNZ    ;  op2 != op1
JNG     Jump if Not Greater             =JLE    ;  op2 <= op1
JNGE    Jump if Not Greater or Equal            ;  op2 < op1
JNL     Jump if Not Less                =JGE    ;  op2 > op1
JNLE    Jump if Not Less or Equal               ;  op2 >= op1
 
; Saut inconditionnel
; -------------------
JMP     Jump                                    saut inconditionnel
 
; Sauts conditionnels et test sur les données
; -------------------------------------------
JNO     Jump if Not Overflow                    ;  saut s'il n'y pas de dépassement
JNP     Jump if Not Parity                      ;  saut si pas de bit de parité
JNS     Jump if Not Sign                        ;  saut si pas de bit signé
JNZ     Jump if Not Zero                        ;  saut si différent de zéro
JO      Jump if Overflow                        ;  saut si dépassement
JP      Jump if Parity                  =JPE    ;  saut si de bit de parité paire
JPE     Jump if Parity Even             =JP     ;  saut si de bit de parité paire
JPO     Jump if Parity Odd              =JNP    ;  saut si de bit de parité impaire
JS      Jump if Sign                            ;  saut si bit signé
JZ      Jump if Zero                    =JE     ;  saut si égal à zéro
 
; Chargements par adresses
; ------------------------
LAHF    Load AH with Flags                      ;  bits arithmétique du "flag-reg"
LDS     Load pointer to DS                      ;  adresse de op2 -> DS:op1
LEA     Load Effective Address                  ;  adresse de op2 -> op1
LES     Load pointer to ES                      ;  adresse de op2 -> ES:op1
LOCK    LOCK the bus for 1 cycle                ;  réservation du bus pour 1 cycle
LODB    Load Byte                               ;  C&ocirc;ne-mémoire -> AL
LODW    Load Word                               ;  C&ocirc;ne-mémoire -> AX
 
; Boucle, on suppose que CX a une valeur avant de débuter la boucle
; -----------------------------------------------------------------
LOOP    LOOP while CX != 0                      ;  branchement si CX = 0
LOOPZ   LOOP while Zero               =LOOPE    ;  branchement si CX = 0 et ZF = 1
LOOPNZ  LOOP while Not Zero           =LOOPNE   ;  branchement si CX = 0 et ZF = 0
LOOPE   LOOP while Equal              =LOOPZ    ;  branchement si CX = 0 et ZF = 1
LOOPNE  LOOP while Not Equal          =LOOPNZ   ;  branchement si CX = 0 et ZF = 0 


Source: Net Hackers du juillet-aoàt 2006 / FaSm

Ainsi nous allons changer nos JE en JNE ...

L'adresse (offset) de l'octet se situe tout en bas de l'écran : 0x72C -ou- 72Ch , le '0x' ou le 'h' signifie 'hexadécimal'

Il faut bien sûr faire une copie du programme avant de l'éditer. J'aime bien l'idée de laisser le programme d'origine et de faire deux copies :
"asm de crackme.exe" et "hexedit de crackme.exe"
Maintenant il n'y que la valeur à changer, dans notre cas les 4 valeurs:

Comme ci-dessous.



[2] Ecraser les tests

Le but lors de l'écrasement de tests est tout simplement de lui de ne RIEN FAIRE. Pour ne rien faire, il existe en assembleur, une instruction codée sur 1 octet appelée NOP. NOP signigie "No OPeration". Pour reverser, cracker une application le NOP n'est pas toujours la solution bien qu'il soit très souvent utilisé. Voici la zone de test ci-dessous que nous allons écraser avec notre instructon.

Si le programme ne fait aucun tests et ne fait rien au lieu d'afficher "perdu!" alors il se contentera d'exécuter à partir de "gagne :)"
Dans l'éditeur héxadécimal il suffit de ne mettre que des NOP depuis l'adresse du programme 00401128 jusqu'à 00401114D :


 ; Depuis  0x728, offset dans le programme 
 
:004011128        cmp byte ptr  [ebp-04] , 61 
 
 ; Jusqu'à 0x74D 
 
:00401114D        push 0040C10A              

on aura donc tout les valeurs en rouge à mettre à 0x90, 90 en héxadécimal :


files/langage_c/reversing/before_nops.gif

-> devient ->
files/langage_c/reversing/writes_nops.gif


[3] Deviner le mot de passe


Dans ce cas là il n'est pas trop dur à lire...


files/langage_c/reversing/guess_password.gif


On peut le voire dans l'éditeur héxadécimal...


files/langage_c/reversing/read_password.gif

le mot de passe est donc azer



[4] Forcer le test pour qu'il soit toujours vrai

L'idée est de réaliser un test sur le buffer lui-même plutot que sur les caractères. Effectivement le test sera toujours vrai.
Le bout de programme ci-dessous "signifie" en langage interpretable :


En considérant que ptr [ebp-04]  est une variable :

comparer la variable à 0x61

si le test est vrai alors aller en 0040114D

            deviendra

comparer la variable à la variable

si le test est vrai alors aller en 0040114D

le test ira donc toujours en 0040114D > "gagne :)" 

D'après le code que l'on peut voir :


files/langage_c/reversing/always_true.gif

807DFC61        cmp byte ptr  [ebp-04] , 61 
 
 ; 807D =  cmp byte       
 ; FC   =  ptr  [ebp-04]    
 ; 61   =  'a'            
 
807DFCFC        cmp byte ptr  [ebp-04] , ptr  [ebp-04]   




0x07. CODING


Nous allos donc maintenant coder le programme ne C qui permettra de patcher un crackme générique.

Voici un bout de code en C qui va permettre de tester si on est sur le bon octet et si c'est le cas
le remplacer la valeur :


1- D'abord on ouvre le fichier en mode "mise à jour" c'est à dire "r+b"


flux_fichier=fopen(nom_de_fichier,"r+b"); 

2- Ensuite on se positionne sur l'offset trouvé avec WinDasm, avec fseek et le paramètre SEEK_SET


 fseek(flux_fichier,OFFSET,SEEK_SET);        
 test=fgetc(flux_fichier); 

3- Il faut tester si on est sur le bon caractère :


if( test != 0x74 )   // octet à tester, par exemple 0x74, si ce n'est pas le bon, 
 {                    // on avertit et quitte le programme                         
      printf("/nCe n'est pas la bonne version du programme./n");                 
      exit(1);                                                                     
 } 

4- On revient au début du fichier pour se repositionner car avec fgetc, fseek on avance d'un caractère, il faut revenir au début :


 rewind(flux_fichier); 

5- Ensuite on se positionne sur l'offset trouvé avec WinDasm cette fois ci pour mettre la bonne valeur :


 fseek(flux_fichier,OFFSET,SEEK_SET);  
 fputc(VALEUR_CRACKEE,flux_fichier); 

6- Tout ceci est à faire autant d'octets il y a à patcher, donc dans une boucle.





0x08. CONCLUSION


Voici les programmes utilisés au cours de ce tutoriel

Lien : Le programme qui patch le 'crackme'
Lien : Source complète du crackme




   =>   Écrit par : Nicolas, le 25 juillet 2015


 
Mots clés :  
  reverse 
  
  windows 
    >   Articles connexes :

GDB - Cas d'école



Format String


Le "format string" est une vulnérabilité lié au paramètre qu'il est possible de donner à l'instruction printf et ses dérivés. Nous allons l'exploiter dans un cas d'école

Comment gagner du temps sur Internet



Durcissement de Windows



3808200