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
|
|