Crackme V3.0 de Cosh et FTP Xpert 1.30

By Christal

CrackMe 3 de Cosh

Commençons pas délimiter notre "espace de travail"...
Celui ci va être compris, à priori, entre la saisie des champs du CrackMe et l'affichage de la MessageBox "code invalide"...

La saisie des messages, au moins dans les programmes écrit en C (ces API ne sont pas sollicitées par les programmes écrits en Delphi ou en VB), se fait généralement par le biais des API GetWindowTextA et GetDlgItemTextA. (according to Rhayader).
Pour commencé, j'ai utilisé le breakpoint "action" suivant:

GetWindowtext, déclaré comme ceci:

int GetWindowtext (HWND hWnd, LPTSTR IpString, int MaxCount),

Au moment du break, la pile (stack) ressemblera à ceci:

[ESP+0Ch]    - contiendra nMaxCount
[ESP+08h]    - contiendra IpString
[ESP+04h]    - contiendra hwnd
[ESP+00h]    - contiendra le Return EIP

Au retour de la fonction API appelée, GetWindowText placera le texte saisi à l'adresse pointée par IpString, si bien qu'il faudra utiliser le caractère * pour lire le contenu de l'adresse pointée. (D *ESP+8)

En posant un point d'arrêt de ce genre, vous obtiendrez un break avec affichage du contenu du champ saisi:

Bpx GetWindowTextA Do "D esp->8;", et en appuyant sur F12 vous reviendrez à la fonction appelante.
Pour vous épargner le F12, vous pouvez aussi entrer:

Bpx GetWindowTextA Do "D esp->8;P RET;" qui vous ferra retourner au call appelant automatiquement.

L'API GetDlgItemText fonctionne sur le même principe, mais les adresses ne seront pas les mêmes. Une autre différence sera
nIDDlgItem, qui est l'ID du contrôle qui saisi le texte de la pile:

[ESP+10h]    - contiendra nMaxCount
[ESP+0Ch]    - contiendra IpString
[ESP+08h]    - contiendra nIDDlgItem
[ESP+04h]    - contiendra hwnd
[ESP+00h]    - contiendra le Return EIP

Le breakpoint à poser sera alors:

Bpx GetDlgItemTextA Do "D esp->C; P RET;"

Au break sur le BPX posé, vous aurez le contenu du buffet de saisi d'affiché dans la fenêtre des Datas. Il ne suffira plus que de poser un BPR adresse_de_début adresse_de_fin du champ saisi pour pouvoir suivre à la trace ce qu'il va advenir du contenu de cette adresse (vous pouvez vous attendre à ce que ce contenu soit transvasé dans une [mémoire] de travail du genre [EBP-20], sur laquelle il faudra poser un nouveau BPR).

Cette "méthode" est finalement sensiblement équivalente à la recherche du sérial entré en mémoire par un:
S 0 L FFFFFFFF "sérial entré", à la différence que vous pouvez mieux suivre les manipulations faites sur le contenu du champ saisi.

Dans le cas de notre
crackme, voici la totalité de la routine du générateur:

:00401533  CALL      0040189E            > saisi du 1er champ
(retour à cette adresse après un F12, qui suit le break dans SoftIce)
:00401538  MOV       ECX,[EBP-20]        
:0040153B  ADD       ECX,000000E4
:00401541  PUSH      ECX                 > D * ECX = "nom entré"
:00401542  MOV       ECX,[EBP-20]
:00401545  ADD       ECX,60
:00401548  CALL      0040189E            > saisi du 2ème champ
:0040154D  MOV       EDX,[EBP-20]
:00401550  ADD       EDX,000000E0
:00401556  PUSH      EDX                 > D * EDX = "sérial entré"
:00401557  LEA       ECX,[EBP-1C]        > [EBP-1C]= nom -> ECX
:0040155A  CALL      00401898            > call MFC42
:0040155F  MOV       EAX,[EBP-20]
:00401562  ADD       EAX,000000E4
:00401567  PUSH      EAX                 > D * EAX = "sérial entré"
:00401568  LEA       ECX,[EBP-10]        > [EBP-10]= sérial -> ECX
:0040156B  CALL      00401898            > call MFC42
:00401570  XOR       EAX,EAX             > mise à 0 des registres
:00401572  XOR       EBX,EBX             > qui vont être utilisés
:00401574  XOR       ECX,ECX             > pour le cryptage 
:00401576  MOV       ECX,00000001        > initialisation de la clé de cryptage
:0040157B  XOR       EDX,EDX             > mise à 0 du registre EDX
:0040157D  MOV       EAX,[EBP-1C]        > place le nom dans EAX
:00401580  MOV       BL,[EAX]            > met un caractère du nom dans BL    
:00401582  XOR       BL,CL               > crypte ce caractère
:00401584  MOV       [EAX],BL            > le place dans EAX
:00401586  INC       ECX                 > ajoute 1 à la clé de cryptage
:00401587  INC       EAX                 > caractère suivant
:00401588  CMP       BYTE PTR [EAX],00   > reste-t-il des caractères?
:0040158B  JNZ       00401580            > oui -> boucle       
:0040158D  XOR       EAX,EAX             > non, passe au sérial
:0040158F  XOR       EBX,EBX             > en remettant les 
:00401591  XOR       ECX,ECX             > registres à 0
:00401593  MOV       ECX,0000000A        > initialisation de la clé de cryptage
:00401598  XOR       EDX,EDX       
:0040159A  MOV       EAX,[EBP-10]        > place le sérial dans EAX
:0040159D  MOV       BL,[EAX]            > 1 caractère du sérial dans BL
:0040159F  XOR       BL,CL               > crypte ce caractère
:004015A1  MOV       [EAX],BL            > le place dans EAX
:004015A3  INC       ECX                 > ajoute 1 à la clé de cryptage
:004015A4  INC       EAX                 > caractère suivant
:004015A5  CMP       BYTE PTR [EAX],00   > encore des caractères ?
:004015A8  JNZ       0040159D            > oui -> boucle
:004015AA  MOV       EAX,[EBP-1C]        > place le nom crypté dans EAX
:004015AD  MOV       EDX,[EBP-10]        > place le sérial crypté dans EDX
:004015B0  XOR       ECX,ECX             > ECX mis à 00
:004015B2  MOV       BL,[EAX]            > 1 caractère du nom dans BL
:004015B4  MOV       CL,[EDX]            > 1 caractère du sérial dans CL
:004015B6  CMP       BL,CL               > compare ces 2 caractères
:004015B8  JNZ       004015C3            > si <> saute en bad boy
:004015BA  INC       EAX                 > caract suivant du nom
:004015BB  INC       EDX                 > caractère suivant du sérial
:004015BC  CMP       BYTE PTR [EAX],00   > encore des caractères pour le nom ?
:004015BF  JNZ       004015B0            > oui -> boucle
:004015C1  JMP       004015D9            > non -> le code est OK
:004015C3  PUSH      00                  > routine Bad Boy
:004015C5  PUSH      0040306C            > pousse sur la pile, les
:004015CA  PUSH      00403040            > infos de la MessageBox
:004015CF  MOV       ECX,[EBP-20]
:004015D2  CALL      00401892            > affichage de la MessageBox
:004015D7  JMP       004015ED
:004015D9  PUSH      00                  > routine Good Boy
:004015DB  PUSH      00403034     
:004015E0  PUSH      00403020
:004015E5  MOV       ECX,[EBP-20]
:004015E8  CALL      00401892
etc....

Analysons un peu ces lignes:

· Le sérial et le nom sont saisis par l'API GetWindowTextA
· Le contenu du 1er champ est placé à l'adresse pointée par EAX
· Les caractères sont chargés 1 a 1 dans BL
· le Xor BL, CL va crypter ce caractère: en début de routine, CL va recevoir la valeur 01 qui va être la clé de cryptage. Cette clé changera à chaque fois par incrémentation.
· Puis le caractère crypté va être replacé dans EAX, et remplacera le caractère entré.

Le principe va être le même pour le sérial, à ceci prés que la clé de cryptage sera égale à 10d (0Ah) au démarrage. Puis les caractères du sérial et du nom vont être comparés un à un, jusqu'à concurrence du nombre de caractères contenus dans le 1er champ du formulaire de saisie (le cmp byte ptr [EAX], 00 vérifie que le dernier byte saisie n'est pas un 00). Le sérial peut donc avoir n'importe quel taille supérieure à celle du nom.

Le principe de la validation du sérial est donc basé sur ce schéma:

1er champ Xor CL = 2eme champ Xor CL+10

avec CL qui variera de 01 à {01+ nombre de caractères entrés dans le champ}.

A titre d'exemple, en entrant "Christal" dans le premier champ, vous allez obtenir "bjqmvrfd" après cryptage, et le second champ donnera ">35>=:" pour "489335" d'entré.
Ensuite, "b" -> 6A sera comparé à ">" -> 3E, et vous irez en routine Bad Boy.
Tombeur 4, dans une étude de ce Crackme, avait déjà montré comment patcher le programme pour obliger celui ci à aller systématiquement en routine Good Boy:

:004015B8  7509          jne       004015C3

Il peut y avoir d'autres solutions:

:004015AA  8B45E4        MOV       EAX,[EBP-1C]
:004015AD  8B55F0        MOV       EDX,[EBP-10]
:004015B0  33C9          XOR       ECX,ECX
:004015B2  8A18          MOV       BL,[EAX]
:004015B4  8A0A          MOV       CL,[EDX]
:004015B6  3AD9          CMP       BL,CL
:004015B8  7509          JNZ       004015C3

En 004015AD vous pouvez remplacer EBP-10 (sérial crypté -> 8B55F0) par EBP-1C (nom crypté -> 8B55E4), si bien qu'en fait le nom que vous aurez entré sera comparé avec lui même, et vous irez en routine Good Boy.

Un noppage du JNZ 004015C3 vous ammènes également vers la routine Good Boy.

Vous pouvez aussi supprimer les deux clés de cryptage (Xor BL, CL) et entrer le même sérial (ou nom) dans les deux champs. Là encore vous irez en routine good Boy. Pas très intéressant...

Nous verrons tout à l'heure comment s'amuser avec ces différentes options...

Le plus simple est quand même de trouver quel devrait être le sérial à entrer pour le nom de votre choix:
Dans le cas de cryptage comme celui ci, on ne peut pas établir une grille de proportion (du genre si "4" -> 52 Ascii = ">" -> 62 Ascii, alors "8" -> 56 Ascii = valeur ascii du bon sérial).

Comme on sait que le nom crypté (clé 01) va être comparé au sérial crypté (clé 0A), il est facile (mais cela oblige à utiliser un débuggeur, ou à écrire une routine dans le langage de son choix) d'entrer votre nom dans le premier champ, et votre nom crypté dans le second, soit:

1er  champ:   Christal
2eme champ:   bjqmvrfd

Puis de provoquer un break en 004015AD pour lire le sérial crypté par un " d EDX".
Dans mon cas, j'obtiens "ha}`x}vu". Il ne reste plus qu'à remplir la dialogbox ainsi:

1er  champ:   Christal
2eme champ:   ha}`x}vu

"Christal" et "ha}`x}vu", une fois crypté vont donner "bjqmvrfd", et la comparaison dans la boucle en 004015B6 nous enverra vers la routine Good Boy!
CQFD!

Nous voici maintenant à la tête de tout un panel de possibilités.
Laquelle choisir?
Pourquoi pas toutes?

J'ai donc opté pour un "
General anti-Cosh3" qui pourrait aussi bien patcher le programme (mais uniquement en mémoire) que de délivrer le bon serial en reprennant le générateur de code. Ayant utilisé la bonne vieille technique du Coupé/Collé, les commentaires de l'extrait du source ci dessous sont identiques à ceux du Dead Listing.
Je tient à préciser que n'étant pas programmeur du tout, les extraits de source (ormi ceux que j'ai reproduit) sont très loin d'être optimisés, et pour tout dire, probablement très mal écrit. Sorry!

      invoke GetWindowTextA,hwndEdit1,ADDR buffer,13 
      > saisie limite à 13, la taille de mon champ d'édition

;========================================================================= 
;                             Key File generator
;=========================================================================  
      cmp       eax ,2                ; 2 caractères mini à entrer
      jl        Trop_court
      XOR       EAX,EAX               ; mise à zéro de tous les registres
      XOR       EBX,EBX               ; de travail
      XOR       ECX,ECX
      XOR       EDX,EDX
      INC       ECX                   ; initilisation de la clé de cryptage
      LEA       EAX,[offset buffer]   ; eax = Name
loop1:
      MOV       BL,[EAX]              ; BL = caractère du Name
      XOR       BL,CL                 ; CL = clé de cryptage
      MOV       [EAX],BL              ; sauvegarde du résultat
      INC       ECX                   ; incrémente clé de cryptage
      INC       EAX                   ; passe au caractère suivant  
      CMP       BYTE PTR [EAX],00     ; est-ce le dernier caractère?
      JNZ       loop1                 ; Non -> loop
      XOR       EAX,EAX               ; remise à zéro des registres
      XOR       EBX,EBX
      XOR       ECX,ECX
      XOR       EDX,EDX               
      MOV       ECX,0000000Ah         ; clé de cryptage = 0Ah
      LEA       EAX,[offset buffer]   ; EAX = Name crypyté
loop2:
      MOV       BL,[EAX]              ; un caractère dans BL
      XOR       BL,CL                 ; crypté avec la clé
      MOV       [EAX],BL              ; et replacé dans le buffer
      INC       ECX                   ; incrémente la clé de cryptage
      INC       EAX                   ; passe au caractère suivant
      CMP       BYTE PTR [EAX],00     ; est-ce le dernier
      JNZ       loop2                 ; Non -> loop
      jmp       Affiche               ; fin de la génération du bon serial

Dans l'un de ses extraordinaires textes sur la programmation ASM, Izcelion proposait une routine permettant de patcher un programme en mémoire. Par chance, Morgatte nous en a fait une traduction fidèle et inspirée qui vous donnera toutes les explications (voir Dossier n°9):

      .IF  ax==IDM_patch                           ; menu "Patch" selectionné?
;=========================================================================
;             issue d'un exemple d'Izcelion traduit par Morgatte
;=========================================================================
   cmp       byte ptr [Flag_process],01            ; le process est déjà créé?
   jne       run_memory                            ; alors Go_Out
           
   invoke    SetWindowTextA,hwndEdit2,ADDR in_use  ; affichage dans
   invoke    SetWindowTextA,hwndEdit1,ADDR in_use2 ; les champs d'édition
   jmp       Go_out_memory

run_memory:          
   invoke   GetStartupInfo,addr startinfo  ; récupère les propriétés du process    
   invoke   CreateProcess, addr Name_of_exe, NULL, NULL, NULL, FALSE, \
   DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, \ 
   addr     startinfo, addr pi             ; mémorise les Process_informations
         
   cmp   eax,0                             ; si la création du process a échoué
   jne   File_founded                      ; c'est que le fichier n'a pas été trouvé
   invoke GetOpenFileName, ADDR ofn        ; ouverture de la boite OpenFile

.if eax==TRUE 
   invoke GetStartupInfo,addr startinfo    ; et on relance la machine...
   invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \
   DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, \ 
   addr startinfo, addr pi
.endif

File_founded:
     	 .while TRUE                         ; début de la boucle Debug Apis
	 invoke WaitForDebugEvent, addr DBEvent, INFINITE

	 .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
      				
	 .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT      
                                           ; préparation affichage informations
		invoke wsprintf, addr buffer, addr ProcessInfo, \
		  DBEvent.u.CreateProcessInfo.hFile, \
      	  DBEvent.u.CreateProcessInfo.hProcess, \
      	  DBEvent.u.CreateProcessInfo.hThread, \
      	  DBEvent.u.CreateProcessInfo.lpBaseOfImage, \
      	  DBEvent.u.CreateProcessInfo.lpStartAddress, \
              offset1, patch1
      
           invoke MessageBox,0, addr buffer, addr AppName, \
      	  MB_OK+MB_ICONINFORMATION      ; affichage des informations sur le process
      
      	invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, \
      	  offset1, addr patch1, 2, NULL ; le process est patché en mémoire    
      				
      	 .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
      	   .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
      		   invoke ContinueDebugEvent, DBEvent.dwProcessId, \
      		   DBEvent.dwThreadId, DBG_CONTINUE.continue
      		   .endif                 ; sortie de la boucle
      		 .endif
      	invoke ContinueDebugEvent, DBEvent.dwProcessId, \  ; fin de la boucle
      	DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
      .endw

     invoke CloseHandle,pi.hProcess        ; fermeture du process
     invoke CloseHandle,pi.hThread
     mov    byte ptr [Flag_process],01     ; Flag anti plantage

Go_out_memory:       
     .ENDIF            

Il y a aussi possible d'utiliser une méthode que Pulsar avait employé dans l'un de ses programmes:
la modification de la valeur placée dans le registre EDX, en la remplacant par celle placée dans EAX.

:004015AA 8B45E4 MOV EAX,[EBP-1C]
:004015AD 8B55F0 MOV EDX,[EBP-10]

Pour cela, je vais utiliser les Debug Registers.
Pour plus d'explication sur ces Debug Apis et les Debug Registers, reportez vous au
Dossier n°9 du Groupe de travail.

;=========================================================================
;                             Debug procédures
;=========================================================================	

contexte proc
        pushad
        mov    dword ptr [context], CONTEXT_i486 \
        or CONTEXT_DEBUG_REGISTERS
        push   offset context
        push   dword ptr [hThread]
        Call   GetThreadContext
        mov    dword ptr [context + 184], 004015B2h    ; regEIP
        or     dword ptr [context+18h], 00000003h      ; DR7
        and    dword ptr [context+18h], FFF0FFFFh      ; DR7
        or     dword ptr [context+14h], 00000001h      ; DR6
        push   offset context
        push   dword ptr [hThread]
        Call   SetThreadContext
        popad
ret
contexte endp

;=========================================================================

contexte2 proc
        pushad
        mov    dword ptr [context], CONTEXT_i486 \
        or CONTEXT_CONTROL or CONTEXT_INTEGER or CONTEXT_SEGMENTS
        push   offset context
        push   dword ptr [hThread]
        Call   GetThreadContext
        mov    eax, dword ptr [context+176d]
        mov    dword ptr [context+168d], eax
        push   offset context
        push   dword ptr [hThread]
        Call   SetThreadContext
        popad
ret
contexte2 endp

;=========================================================================

contexte3 proc
        pushad
        mov    dword ptr [context], CONTEXT_i486 \
        or CONTEXT_DEBUG_REGISTERS
        push   offset context
        push   dword ptr [hThread]
        Call   GetThreadContext
        mov    dword ptr [context+04], 00000000
        and    dword ptr [context+18h], -256
        and    dword ptr [context+14h], FFFFFFF8h
        push   offset context
        push   dword ptr [hThread]
        Call   SetThreadContext
        popad
ret
contexte3 endp

;=========================================================================
;           source reconstitué à partir d'un programme de Pulsar
;=========================================================================

debug_patch proc 
          invoke    SetWindowTextA,hwndEdit2,NULL
          cmp       byte ptr [Flag_process],01     ; le process est déjà créé?
          jne       run_proc
          invoke    SetWindowTextA,hwndEdit2,ADDR in_use 
          invoke    SetWindowTextA,hwndEdit1,ADDR in_use2 
          jmp       Go_out_process
run_proc:          
          invoke    MessageBoxA,NULL, ADDR Name_entered, ADDR run_process, MB_OK
          invoke    SetWindowTextA,hwndEdit1,ADDR methode 
          
          push      offset StartupInfo
          Call      GetStartupInfoA
          
          invoke   CreateProcess, addr Name_of_exe,  addr Name_of_exe, NULL, NULL,\
          FALSE, DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS , NULL,\ 
          NULL, ADDR StartupInfo, ADDR lp_process_info
          
          cmp       eax,0
          jne       File_founded2
          invoke    GetOpenFileName, ADDR ofn

    .if eax==TRUE 
          invoke   CreateProcess, addr buffer,  addr buffer, NULL, NULL,\
          FALSE, DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS , NULL,\ 
          NULL, ADDR StartupInfo, ADDR lp_process_info
    .endif

File_founded2:
          mov      esi, dword ptr [lp_process_info+04]
          mov      dword ptr [hThread], esi  
          mov      esi, dword ptr [lp_process_info+08] 
          mov      dword ptr [hProcess], esi 
 
          Xor       esi,esi
          jmp       continue2
loop_debug:
          push      esi
          invoke    WaitForDebugEvent,ADDR debug_event,FFFFFFFFh
          cmp       dword ptr [debug_event],CREATE_PROCESS_DEBUG_EVENT
          jne       continue1
          call      contexte
          
          invoke ContinueDebugEvent, debug_event.dwProcessId,\
          debug_event.dwThreadId, DBG_CONTINUE 
          jmp       do_continue
continue1:
          cmp       dword  ptr [debug_event], 00000001
          jne       continue3
          mov       dword  ptr [lp_process_info+10h],CONTEXT_i486 or CONTEXT_CONTROL\
          or CONTEXT_INTEGER or CONTEXT_SEGMENTS
          push      offset lp_process_info +10h
          push      dword  ptr [lp_process_info+04]
          Call      GetThreadContext
          cmp       dword  ptr [lp_process_info+200d], 004015b2h
          jne       continue4
          
          call      contexte2
          call      contexte3
continue4:
          invoke ContinueDebugEvent, debug_event.dwProcessId,\
          debug_event.dwThreadId, DBG_CONTINUE 
          jmp       do_continue
continue3:
          cmp       dword ptr [debug_event], 00000005
          jne       not_handled
          pop       esi
          jmp       Go_out_process
          jmp       do_continue
not_handled:
          invoke ContinueDebugEvent, debug_event.dwProcessId,\
          debug_event.dwThreadId, DBG_CONTINUE 
do_continue:
          pop       esi
continue2:
          or        esi, esi
          je        loop_debug
 
Go_out_process:
          mov       byte ptr [Flag_process],01
          PUSH      DWORD PTR [hProcess]
          CALL      CloseHandle 
          PUSH      DWORD PTR [hThread]
          Call      CloseHandle

          ret
debug_patch endp

Debug Registers (extrait du Dossier n°9)

Vous vous êtes déjà demandé pourquoi les BPM (BreakPoint on Memory) n'étaient limités qu'à quatre?

Tout simplement parce qu'il n'existe, dans l'architecture Win32, que quatre endroits où stocker les adresses lineaires de ces breakpoints: DR0, DR1, DR2, DR3. Les DR4 et DR5 sont réservés, le DR7 "configure" le type de Break à exécuter à une adresse donnée, le DR6 sert aux debuggeurs.

Reprennont l'exemple du source ci dessus: activation du BreakPoint

        mov    dword ptr [context+B8h], 004014E1h      ; regEIP
        or     dword ptr [context+18h], 00000003h      ; DR7
        and    dword ptr [context+18h], FFF0FFFFh      ; DR7

Pour être exact, il faudrait l'écrire ainsi:

        mov    dword ptr [context+B8h], 004014E1h                         ; regEIP
        or     dword ptr [context+18h], 00000000000000000000000000000011b ; DR7
        and    dword ptr [context+18h], 11111111111100001111111111111111b ; DR7

La représentation en binaire va permettre de mieux comprendre le fonctionnement global.

Dans une architecte 32 bytes, le tableau général des Debug Registers est le suivant:

      31  29  27  25  23  21  19  17  15    12    9 8 7 6 5 4 3 2 1 0
     |---+---+---+---+---+---+---+---+---+-+-----+-+-+-+-+-+-+-+-+-+-|
     |LEN|R/W|LEN|R/W|LEN|R/W|LEN|R/W|   | |     |G|L|G|L|G|L|G|L|G|L|
     |   |   |   |   |   |   |   |   |0 0|0|0 0 0| | | | | | | | | | | DR7
     | 3 | 3 | 2 | 2 | 1 | 1 | 0 | 0 |   | |     |E|E|3|3|2|2|1|1|0|0|
     |              (10 à 12 et 14 à 15 sont reserved)               |
     |---+---+---+---+---+---+---+---+-+-+-+-----+-+-+-+-+-+-+-+-+-+-|
     |                               |B|B|B|                 |B|B|B|B|
     |0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0| | | |0 0 0 0 0 0 0 0 0| | | | | DR6
     |                               |T|S|D|                 |3|2|1|0|
     |---------------+---------------+-+-+-+---------+-------+-+-+-+-|
     |                            RESERVED                           | DR5
     |---------------+---------------+---------------+---------------|
     |                            RESERVED                           | DR4
     |---------------+---------------+---------------+---------------|
     |                 BREAKPOINT 3 LINEAR ADDRESS                   | DR3
     |---------------+---------------+---------------+---------------|
     |                 BREAKPOINT 2 LINEAR ADDRESS                   | DR2
     |---------------+---------------+---------------+---------------|
     |                 BREAKPOINT 1 LINEAR ADDRESS                   | DR1
     |---------------+---------------+---------------+---------------|
     |                 BREAKPOINT 0 LINEAR ADDRESS                   | DR0
     |---------------+---------------+----------------+--------------|

Les bytes 10 à 15 (reserved):
31                  15 14 13 12 11 10         0
+------------------+--+--+--+--+--+--+--------+
|                  | T| T| G| I|  |  |        |
|                  | 2| R| D| R|  |  |        |
+------------------+--+--+--+--+--+--+--------+
                     |  |  |  |
                     |  |  |  +- IceBp    1=INT01 causes emulator
                     |  |  |              to break emulation
                     |  |  |              0=CPU handles INT01
                     |  |  +---- Global   Debug 
                     |  +------- Trace1   1=Generate special address
                     |                    cycles after code dis-
                     |                    continuities.  On Pentium,
                     |                    these cycles are called
                     |                    Branch Trace Messages.
                     +---------- Trace2   1=Unknown.

Dans l'exemple utilisé, les OR et AND vont servir à conserver les informations du DR7 (d'éventuels autres BPM), et à ne modifier que celles qui vont directement concerner le DR0 dans lequel le BreakPoint attendu (en 004014E1) va être posé. Si aucun BreakPoint nest posé, les bits sont tous à 0.

      31  29  27  25  23  21  19  17  15    12    9 8 7 6 5 4 3 2 1 0
     |---+---+---+---+---+---+---+---+---+-+-----+-+-+-+-+-+-+-+-+-+-|
     |LEN|R/W|LEN|R/W|LEN|R/W|LEN|R/W|   | |     |G|L|G|L|G|L|G|L|G|L|
       00  00  00  00  00  00  00  00  00 0  0 00 0 0 0 0 0 0 0 0 1 1
 +     11  11  11  11  11  11  00  00  11 1  1 11 1 1 1 1 1 1 1 1 1 1
 =    -----------------------------------------------------------------
       00  00  00  00   00  00 11  11  00 0  0 00 0 0 0 0 0 0 0 0 1 1
                               ------                             ---
                                  |                                | 
                                  +---------------DR0--------------+

Quelques explications:

Debug Address Registers (DR0-DR3)

Chacun de ces registres contient l'adresse lineaire (par exemple 00401000) associée avec l'une des quatre conditions possibles. Ces conditions sont définies dans le DR7.

Debug Control Register (DR7)

A chaque adresse lineaire des registres DR0 à DR3, correspond un champ R/W. Le processeur interprète ces bits ainsi:

00 -- Break on instruction execution only
01 -- Break on data writes only
10 -- undefined
11 -- Break on data reads or writes but not instruction fetches

Les champs LEN0 à LEN3 précisent, quant à eux, la longeur des items à monitorer.
Les valeurs des champs sont interprétés de cette façon:

00 -- one-byte length
01 -- two-byte length
10 -- undefined
11 -- four-byte length

Si RWn est egal à 00 (instruction execution), alors LENn doit aussi être égal à 00. Toute autre valeur est ignorée.

Les champs L0 à L3 (Local) et G0 à G3 (Global), également lié à l'un des quatre DR0-DR3, activent (ou désactivent suivant l'état) l'adresse du breakpoint selectionné.

Debug Status Register (DR6)

Le DR6 sert principalement aux débuggeurs pour déterminer quelle "debug condition" est arrivée. Dans le source ci dessus, il n'est pas nécessaire de s'en occuper.



FTP Xpert 1.30

Dans ce second exemple de générateur, le serial est tout aussi facile à trouver, voir plus facile encore, mais son générateur donne une impression un peu brouillon...

Définition des champs de recherche:

en premier, il convient de trouver:

Où le serial est saisi:                                                        BPX Hmemcpy
Où la MessageBox "mauvais numéro" s'affiche:         Trace F10

puis de rechercher les contrôles éventuels.
Dans ce programme, la lecture est assez facile, et pour tout dire classique:

Saisie du name
Si absent Go Out
Saisie du serial entré
Si absent Go Out
Saisie du Name
Génération du bon serial
Saisie du Serial entré
Comparaison
Si différent, affichage d'une autre MessageBox "mauvais code".

:004BAOB2 call 0043F328                          saisie du Name
:004BAOB7 cmp  dword ptr [ebp-08], 00000000      si absent
:004BAOBB je   004BA1C9                          Goto end
:004BAOC1 lea  edx, dword ptr [ebp-0C]
:004BAOC4 mov  eax, dword ptr [ebx+000002D4]
:004BAOCA call 0043F328                          saisie du serial
:004BAOCF cmp  dword ptr [ebp-0C], 00000000      si absent  
:004BAOD3 jne  004BAOF2                          Goto "Mauvais numéro"
:004BAOD5 push 00000030

* Possible StringData Ref from Code 0bj ->"FTP Expert"
:004BAOD7 mov  ecx, 004BA1F8

* Possible StringData Ref from Code 0bj ->"Mauvais num"
:004BAODC mov  edx, 004BA204
:004BAOE1 mov  eax, dword ptr [004F4944]
:004BAOE6 mov  eax, dword ptr [eax]
:004BAOE8 call 00463820
:004BAOED jmp  004BA1C9                          Goto end

* Referenced by a (U)nconditional or (C)onditiona1 Jump at Address:
|:004BAOD3(C)
I
:004BAOF2 lea  edx, dword ptr [ebp-10]
:004BAOF5 mov  eax, dword ptr [ebx+000002D4]
:004BAOFB call 0043F328                          saisie du name
:004BA100 mov  edx, dword ptr [ebp-10]
:004BA103 lea  ecx, dword ptr [ebp-04]
:004BA106 mov  eax, dword ptr [004F4828]
:004BA10B mov  eax, dword ptr [eax]
:004BA10D call 004C60AO                          géneration du bon serial
:004BA112 lea  edx, dword ptr [ebp-14]
:004BA115 mov  eax, dword ptr [ebx+000002D8]
:004BA11B call 0043F328                          saisie du serial
:004BA120 mov  edx, dword ptr [ebp-14]           serial entré dans edx
:004BA123 mov  eax, dword ptr [ebp-04]           bon serial dans eax
:004BA126 call 00404138                          contrôle des deux serials
:004BA12B jne  004BA1Bl                          bad boy

etc...

* Possible StringData Ref from Code 0bj ->"FTP Expert"
:004BA171 mov  ecx, 004BA1F8

* Possible StringData Ref from Code 0bj ->"Merci de votre enregistrement. "
                                        ->"Le logiciel est maintenant assign"
:004BA176 mov  edx, 004BA228
:004BA17B mov  eax, dword ptr [004F4944]
etc...
:004BA1AF jmp  004BA1C9

* Possible StringData Ref from Code 0bj ->"FTP Expert"
:004BA1B3 mov  ecx, 004BA1F8

* Possible StringData Ref from Code 0bj ->"Mauvais num"
:004BA1B8 mov  edx, 004BA204
:004BA1BD mov  eax, dword ptr [004F4944]
etc...
:004BA1EB ret

Vous imaginez bien que c'est dans le call 004C60A0 que nous allons trainer nos guêtres...
Pour trouver que ce call était celui du générateur, rien n'a été plus simple, il a suffit de faire s'afficher l'adresse de stockage du bon serial dans la fenêtre des Datas, et de recommencer le Trace On à partir de la saisie des champs.

EAX=011A2004   EBX=011EBFEC   ECX=0076E2B8   EDX=011EC548   ESI=011EE704        
EDI=0076E448   EBP=0076E2BC   ESP=0076E28C   EIP=004C60A0   o d I s z a p c     
CS=017F   DS=0187   SS=0187   ES=0187   FS=3DCF   GS=0000                       
-----FTPXPERT!CODE+000C5294-----------------------byte--------------PROT---(0)--
0187:004C6294 46 48 59 45 53 54 59 55-4C 50 56 42 4E 4D 43 52  FHYESTYULPVBNMCR
0187:004C62A4 59 35 36 37 41 41 38 32-33 34 34 35 32 31 33 34  Y567AA8234452134
0187:004C62B4 00 00 00 00 FF FF FF FF-01 00 00 00 2D 00 00 00  ............-...
-------------------------------------------------------------------------PROT32-

017F:004C60A0  PUSH      EBP                ; entrée du Call
etc...                                          
017F:004C60B9  MOV       [EBP-04],EDX       ; Name entré
017F:004C60BC  MOV       EAX,[EBP-04]                                           
etc...                                          
017F:004C60D2  LEA       EDX,[EBP-14]                                           
017F:004C60D5  MOV       EAX,004C6294       ; string (voir Data)
017F:004C60DA  CALL      00408EAC           ; routine (voir ci dessous)
017F:004C60DF  LEA       EDX,[EBP-0C]                
017F:004C60E2  MOV       EAX,[EBP-04]       ; Name entré 
017F:004C60E5  CALL      00408EAC           ; routine (voir ci dessous)
017F:004C60EA  XOR       EDI,EDI                       
017F:004C60EC  LEA       EAX,[EBP-10]                                           
017F:004C60EF  CALL      00403DA8                                               
017F:004C60F4  MOV       EAX,[EBP-04]       ; Name entré
017F:004C60F7  CALL      00404028           ; calcul de la taille du Name entré
017F:004C60FC  MOV       ESI,EAX            ; EAX = taille du Name entré

------------------------------------FTPXPERT!CODE+000C50A0----------------------

:00408ECD 8A02   mov a1, byte ptr [edx]     ; Name entré 
:00408ECF 3C61   cmp a1, 61                 ; compare avec "a"
:00408Edl 7206   jb 00408ED9                ; branchement si inférieur
:00408ED3 3C7A   cmp a1, 7A                 ; compare avec "z"
:00408ED5 7702   ja 00408ED9                ; branchement si supérieur
:00408ED7 2C20   sub a1, 20                 ; soustrait 20h à la valeur ASCII
:00408ED9 8806   mov byte ptr [esi], al     ; enregistre le caractère validé
:00408EDB 42     inc edx                    ; passe au caractère suivant
:00408EDC 46     inc esi                    ; incrémente adresse de "réception"
:00408EDD 4B     dec ebx                    ; décrémente la taille du Name
:00408EDE 85DB   test ebx, ebx              ; contrôle qu'il reste des caractères
:00408EE0 75EB   jne 00408ECD               ; sinon boucle

Dans cette boucle, chaque caractère du Name entré va être contrôlé.
Tous les caractères en minuscule (compris entre a et z) vont être transformé en majuscule.

:004C6107 lea eax, dword ptr [ebp-1C]      
:004C610A mov edx, dword ptr [ebp-0C]       ; Name entré en majuscule
:004C610D mov dl, byte ptr [edx+ebx-01]     ; un caractère du name dans DL
:004C6111 call 00403F50                      
:004C6116 mov eax, dword ptr [ebp-1C]       ; EAX = caractère (de DL)
:004C6119 mov edx, dword ptr [ebp-14]       ; string "FHYESTYULPVBNMCR..."
:004C611C call 00404314                     ; cherche la correspondance

le Call 00404314 va servir, à partir d'une lettre prélévée dans le Name Entré, à en rechercher son équivalent dans la string "FHYESTYULPVBNMCR...". Dés qu'il aura trouvé, il sortira du call avec dans EAX la position du caractère.
Par exemple, si le Name que vous avez entré est snake, il deviendra SNAKE, la lettre "S" vaudra 5, "N" vaudra 13, et ainsi de suite...

Si l'une des lettre du Name Entré, converti en majuscule si besoin est, n'est pas trouvée dans la string, la lettre est ignorée. Cette routine boucle sur le Name Entré jusqu'à épuisement du nombre de caractères (fin de la boucle en 004C6141), et génère un Nouveau Name, débarassé des caractères non validés. Ainsi, SNAKE deviendra SNAE, ce qui est déjà moins ppppoétique...

:004C6143 lea eax, dword ptr [ebp-0C]
:004C6146 mov edx, dword ptr [ebp-10]       ; Name en majuscule tronqué: SNAE
:004C6149 call 00403E40                     
:004C614E mov eax, dword ptr [ebp-0C]
:004C6151 call 00404028                     ; mesure la taille du "nouveau Name"
:004C6156 cmp eax, 00000018                 ; fait-il 24 caractères de long? 
:004C6159 jge 004C6189                      ; saut si supérieur ou égal
:004C615B mov esi, 00000018                 ; sinon passe à la suite
etc...

:004C616B lea eax, dword ptr [ebp-24]       
:004C616E mov edx, dword ptr [ebp-14]       ; string "FHYESTYULPVBNMCR..." 
:004C6171 mov dl, byte ptr [edx+ebx-01]     ; dl = un caracatère de la string


De 004C6187 à 004C616B, le programme va boucler jusqu'à créer une nouvelle chaine.
Les premiers caractères vont être ceux du nouveau Name, suivi de la string et à concurence de 24 caracères:

SNAEFHYESTYULPVBNMCRY56A
EAX=011EC590   EBX=00000002   ECX=00000006   EDX=00000001   ESI=00000013        
EDI=00000013   EBP=0076E288   ESP=0076E244   EIP=004C61BC   o d I S z A p C     
CS=017F   DS=0187   SS=0187   ES=0187   FS=3DCF   GS=0000   DS:011EC591=48      
--------------------------------------------------byte--------------PROT---(2)--
0187:011EC590 43 48 52 53 54 41 4C 46-48 59 45 53 54 59 55 4C  CHRSTALFHYESTYUL
0187:011EC5A0 50 56 42 4E 4D 43 52 59-00 C5 1E 01 AC C5 1E 01  PVBNMCRY........
-------------------------------------------------------------------------PROT32-

017F:004C61B9  MOV       EAX,[EBP-0C]    ; EAX pointe sur la nouvelle chaine
017F:004C61BC  MOV       AL,[EBX+EAX-01] ; AL = un caractère de cette chaîne
017F:004C61C0  MOV       [EBP-15],AL     ; placé dans [EBP-15] 
017F:004C61C3  LEA       EAX,[EBP-28]    ; string FHYESTYULPVBNMCRY56AA82344...
017F:004C61C6  MOV       DL,[EBP-15]     ; DL = un caractère de cette string
017F:004C61C9  CALL      00403F50                                                 
017F:004C61CE  MOV       EAX,[EBP-28]    ; EAX = caractère AL
017F:004C61dl  MOV       EDX,[EBP-14]    ; EDX = FHYESTYULPVBNMCRY56AA...
017F:004C61D4  CALL      00404314        ; recherche de la correspondance
017F:004C61D9  MOV       ESI,EAX         ; ESI = place du car AL dans la string
017F:004C61DB  MOV       EAX,[EBP-14]    ; EAX = FHYESTYULPVBNMCRY56AA...

------------------------------------FTPXPERT!CODE+000C51B9----------------------

On arrive maintenant au coeur du générateur.
A l'aide de la nouvelle chaine créée, le programme va de nouveau chercher la position du caractère selectionné dans la string. Par souci de raccourci, nous dirons que cette position s'appellera Pos, et que les résultats liés aux bidouilles de Pos vaudront ResN.

EAX=0076E2B8   EBX=00000019   ECX=00000006   EDX=011EE358   ESI=0000000C        
EDI=0000000C   EBP=0076E288   ESP=0076E244   EIP=004C6247   o d I s Z a P c     
CS=017F   DS=0187   SS=0187   ES=0187   FS=3DCF   GS=0000                       
--------------------------------------------------byte--------------PROT---(2)--
0187:011EE358 36 41 59 35 34 34 2D 4C-50 4E 37 46 41 2D 41 35  6AY544-LPN7FA-A5
0187:011EE368 41 31 33 55 2D 4E 50 46-37 53 42 00 74 E3 1E 01  A13U-NPF7SB.t...
0187:011EE378 74 E3 1E 01 34 00 00 00-00 00 00 00 00 00 00 00  t...4...........
0187:011EE388 00 00 00 00 40 00 00 00-1B 00 00 00 00 00 00 00  ....@...........
-------------------------------------------------------------------------PROT32-

017F:004C61DE  CALL      00404028        ; calcul de la taille de la string 
017F:004C61E3  PUSH      EAX             ; EAX vaut 20h poussé sur la pile 
017F:004C61E4  MOV       EAX,ESI         ; et ESI est égal à Pos
017F:004C61E6  IMUL      ESI             ; Pos x Pos = Res1
017F:004C61E8  SUB       EAX,ESI         ; Res1 - Pos = Res2
017F:004C61EA  ADD       EAX,EDI         ; Res2 + Reste = Res3
017F:004C61EC  POP       EDX             ; récupère 20h sur la pile
017F:004C61ED  MOV       ECX,EDX         ; ECX = 20h
017F:004C61EF  CDQ                       ; Convert Double to Quad
017F:004C61F0  IDIV      ECX             ; Res3 / 20h => reste dans EDX 
017F:004C61F2  MOV       ESI,EDX         ; le reste est mis dans ESI
017F:004C61F4  INC       ESI             ; +1 (au cas ou le reste serait = 0)
017F:004C61F5  MOV       EDI,ESI         ; puis placé dans EDI

------------------------------------FTPXPERT!CODE+000C51EC----------------------

Prenons un exemple concret:
CHRS.....
Le "C" à sa correspondance dans le string "FHYESTYULPVBNMCRY56AA82344..." en 15ème position (0F).

0F x 0F    = E1      Pos x pos
E1 - 0F    = D2      Res1 - Pos
D2 + Reste = D2      (au départ le Reste vaut 00)
D2 / 20    =  6      la partie entière est égal à 6, le Reste est égal à 12
12 +  1    = 13      pointeur dans la string (13h = 19d), soit le chiffre 6  

Ce reste va devenir un pointeur dans la string "FHYESTYULPVBNMCRY56AA82344..."

:004C61F7  mov eax, dword ptr [ebp-14]   ; "FHYESTYULPVBNMCRY56AA82344..."
:004C61FA  mov al, byte ptr [eax+esi-01] ; récupère le caractère pointé dans la string

C'est ce chiffre qui va devenir le premier caractère du bon serial.
Une fois que les dixhuit caractères de la nouvelle chaîne ont été traités, séparés tous les 6 caractères par un séparateur, vous avez votre bon serial.

;========================================================================= 
;                             Generator FTP Xpert  
;=========================================================================
generateur2:

      mov  dword ptr [lg_name], eax     ; enregistre la taille du Name
      xor  ebx,ebx
      xor  edx,edx                      ; incrémente buffer

minus_majus:
      mov  al, byte ptr [ebx+buffer]    ; Al = un caractère du Name entré 
      cmp  al, 00                       ; si c'est le dernier
      je   end_test                     ; le contrôle des caractères est fini
      cmp  al, 61h                      ; compare avec "a"
      jb   superieur                    ; branchement si inférieur
      cmp  al, 7Ah                      ; compare avec "z"
      ja   superieur                    ; branchement si supérieur
      sub  al, 20h                      ; soustrait 20 à la valeur ASCII

superieur:
      mov  byte ptr [ebx+buffer], al    ; enregistre le caractère validé
      inc  ebx                          ; passe au caractère suivant
      jne  minus_majus                  ; sinon boucle

end_test:
      xor  ecx,ecx
      lea  edx, dword ptr [buffer]      ; Name entré en majuscule
      lea  edi, dword ptr [ftpstring]   ; string "FHYESTYULPVBNMCR..."

suivant_name:
      call position                     ; cherche la correspondance
      cmp  esi, 20h                     ; si toute la string a été traitée
      jge  non_valid                    ; le caractère n'est pas valide 
      mov  byte ptr [ecx+buffer2],al    ; sinon création d'un nouveau Name
      inc  ecx                          ; incrémente adresse du nouveau Name

non_valid:
      inc  edx                          ; passe au caractère suivant du name
      cmp  byte ptr [edx],00            ; est-ce le dernier ?
      jne  suivant_name                 ; Non -> continue
      invoke lstrcat, ADDR buffer2,ADDR ftpstring ; concatène le Name et la string
      push ebp                          ; sauvegarde du registre EBP
      xor  ebp,ebp                      ; EBP mémorisera le reste de la division
      xor  ebx,ebx                      ; EBX recevra le reste de la division


suite_serial:
      lea   edx, dword ptr [ebx+buffer2]  ; Name entré en majuscule
      lea   edi, dword ptr [ftpstring]    ; string "FHYESTYULPVBNMCR..."
      call  position                      ; cherche la position du caractère
      PUSH  20h                           ; pousse 32 sur la pile
      MOV   EAX,ESI                       ; et la place dans EAX
      IMUL  ESI                           ; Pos x Pos = Res1
      SUB   EAX,ESI                       ; Res1 - Pos = Res2
      ADD   EAX,EBP                       ; Res2 + Reste = Res3
      POP   EDX                           ; récupère 32 sur la pile
      MOV   ECX,EDX                       ; ECX = 32
      CDQ                                 ; Convert Double to Quad
      IDIV  ECX                           ; Res3 / 32 -> reste dans EDX 
      MOV   ESI,EDX                       ; le reste est mis dans ESI
      INC   ESI                           ; +1 (au cas ou le reste serait = 0)
      MOV   EBP,ESI                       ; puis placé dans EDI
      mov   al, byte ptr [esi+edi-01]     ; récupère le caractère pointé dans la string
      mov   byte ptr [ebx+serial],al      ; et le mémorise dans serial
      inc   ebx                           ; incrémente position suivante du serial
      cmp   ebx,20h                       ; est-ce que toute la string a été traitée?
      jl    suite_serial                  ; Non -> loop
      POP   EBP                           ; restore le registre EBP

; "serial" contient tous les caractères du serial. Il reste à placer les séparateurs

      mov   ecx,6                         ; les 6 premiers caractères 
      lea   esi, serial                   ; du serial
      lea   edi, buffer                   ; vont être placé dans le buffer
      rep   movsb 
      mov   byte ptr [buffer+6], "-"      ; mise en place du premier séparateur
      mov   ecx,6                         ; les 6 caractères suivants
      lea   esi, serial+6                 ; du serial généré
      lea   edi, buffer+7                 ; sont placé dans le buffer
      rep   movsb 
      mov   byte ptr [buffer+13], "-"     ; mise en place du séparateur
      mov   ecx,6                         ; etc...
      lea   esi, serial+12
      lea   edi, buffer+14
      rep   movsb 
      mov   byte ptr [buffer+20], "-"
      mov   ecx,6
      lea   esi, serial+18
      lea   edi, buffer+21
      rep   movsb 

Bonne Journée

Christal