Une bien belle image... ;o) COURS

ETUDE DE WINACE 1.2
 
 

PROTECTIONS

ASPACK

KEYFILE

CRC

DATE

01/02/2000

Maj

25/02/2000



Compléments apportés à la version du 01/02/2000

Salut à tous,
 

Petite étude de WinAce.exe Version 1.2
Alors on s'équipe de tout ce qu'il faut : Jus de fruits, barres de céréales...
(certains pour la frime disent : Alcool, drogueS et autres accélérateurs de particules...)
(Essayez de faire une nuit de debug en vous aidant d'un litre de Vodka et vous verrez...)

Il s'agit d'un prog Delphi compacté par AsPack (c'est écrit ds le PE ;o] ).
 
Donc 1ère manipulation : le décompactage

On prend un éditeur de PE header et on vérifie les infos des sections
surtout celles qui contiennent le code et le point d'entrée.

CODE : C0000040 => problèmes = pas de Désassemblage, pas de Debug...
On corrige par un E0000020.

On trace un peu de code avec un Debgr (style SoftICE ou TRW2000)

Première remarque un PUSHAD => il y a de forte chance d'avoir un POPAD à la fin.
 
00648001 PUSHAD *******=Push EDI, ESI, EBP, EBX, EDX, ECX, EAX
00648002 CALL 00648542
00648007 JMP 00648051
00648051 MOV EBX,00442994
00648056 ADD EBX,EBP
00648058 SUB EBX,[EBP+004429C1]
...... On UnPack...
......
006484BC POPAD ******* He Oui
006484BD JNZ 006484C7
006484C7 PUSH 005802E4 Adresse du prog
006484CC RET On y va
005802E4 PUSH EBP On y est ;)
005802E5 MOV EBP,ESP
005802E7 ADD ESP,-10
005802EA PUSH EBX

Donc l'adresse de l'exe est 005802E4
En 006484CC on place un JMP EIP pour faire une boucle infinie...
On prend son meilleur dumper (SDump ou ProcDump ou autre)
On fait un Full Dump de l'exe WinAce (J'aime bien ProcDump).
Ensuite on Kill la tâche WinAce.exe (il y a une boucle infinie)
On prend son meilleur éditeur de PE (ProcDump par exemple ;) et on corrige le point d'entrée :
005802E4 - 00400000 = 001802E4 et voila...
On obtient un exe non compressé qui tourne correctement !
Bien sur on peut attendre d'avoir un prog qui fait du UnAsPack directement
mais bon il faut qd même un minimum de savoir faire pour ne pas être trop dépendant !
 
Petit complément pour un bon Dead Listing : 

La restitution de la table d'import.

Dans l'éditeur de PE en trouve :
idata : VAdrs = 0x001B9000 , VSize = 0x00004000
Donc on peut en conclure que la table se trouvera là en mémoire
(0x00400000 + 0x001B9000 = 0x005B9000) à un moment ou un autre.
On va donc faire un BP sur la zone mémoire indiquée et attendre...
On regarde l'état de la zone mémoire : il s'agit d'adresse mémoire
sur des chaînes (on regarde qq centaines d'octets plus bas si on ni
connait rien et c'est bon si l'on voit des noms de fonctions en clair).
OK on a les infos : Gardons les ds un coin (du Disque dur ;o)
Dump mémoire de 0x005B9000 à 0x005BCFFFF (=x005B9000 + 0x4000 -1).
Nous voilà avec une belle table sur disque.
 

ASTUCE: Si AUCUN DUMPER ne le fait pour vous, il suffit de le
faire sous le debugger et de convertir le fichier texte en binaire...
(Voir en fin de fichier : le code source Pascal d'un convertisseur)
On prend notre meilleur éditeur Hexa et on lui demande d'ajouter ce
fichier à l'exe décompacté ! On fait un build PE amélioré et on se retrouve
avec un beau prog bien lisible (du moins c'est mieux que sans !).
 

Pour ajouter la table ds WinAce : (exp avec Soft Ice comme débuggeur)
> BPM 005B9000 W
Break
> F10 (jusqu'à avoir une table import correcte)
> WD OFF
> D 005B9000 4000
> F5
Ds le loader : Fichier | Save History As... "TableImp.log"
Conversion des lignes du fichier .log en binaire "TableImp.bin"
(il faut bien sur épurer le fichier log pour ne garder que le dump)
Prendre un éditeur de fichier.
Ouvrir les fichiers : WinAceDump.exe et TableImp.bin.
Sélectionner tout le fichier binaire.
Sélectionner la zone 001b9000:001bc540 ds WinAceDump.
Coller (remplacer) ds WinAceDump.
Sauver WinAceDump.exe
Avec un éditeur de PE vous modifiez le "section directory"
au niveau de l'Import table pour y placer les bonnes valeurs.
(Adresse: 001B9000 size: 4000). Vous pouvez aussi reconstruire
le fichier mais pas l'import table (en tout cas moi je ne le fais pas).
Vous voilà avec un bel exécutable bien lisible !
 

Object01: CODE RVA: 00001000 Offset: 00000600 Size: 0017F5CC Flags: E0000020
Object02: DATA RVA: 00181000 Offset: 0017FC00 Size: 0000E480 Flags: C0000040
Object03: BSS RVA: 00190000 Offset: 00190000 Size: 00000000 Flags: C0000040
Object04: .idata RVA: 001B9000 Offset: 0018E200 Size: 00003538 Flags: C0000040
Object05: .edata RVA: 001BD000 Offset: 00191800 Size: 0000012C Flags: C0000040
Object06: .tls RVA: 001BE000 Offset: 001BE000 Size: 00000000 Flags: C0000040
Object07: .rdata RVA: 001BF000 Offset: 00191A00 Size: 00000010 Flags: C0000040
Object08: .reloc RVA: 001C0000 Offset: 00191C00 Size: 00000000 Flags: C0000040
Object09: .rsrc RVA: 001D7000 Offset: 00191C00 Size: 000703BC Flags: C0000040
Object10: .aspack RVA: 00248000 Offset: 00202000 Size: 00001B10 Flags: E0000020
Object11: .rsrc RVA: 0024A000 Offset: 00203C00 Size: 00000000 Flags: C0000040
 

IMPORTED FUNCTIONS: Imported Modules = 24 (decimal)

Import Module 001: kernel32.dll
Import Module 002: user32.dll
Import Module 003: advapi32.dll
Import Module 004: oleaut32.dll
Import Module 005: kernel32.dll
Import Module 006: advapi32.dll
Import Module 007: kernel32.dll
Import Module 008: mpr.dll
Import Module 009: version.dll
Import Module 010: gdi32.dll
Import Module 011: user32.dll
Import Module 012: ole32.dll
Import Module 013: oleaut32.dll
Import Module 014: comctl32.dll
Import Module 015: winspool.drv
Import Module 016: shell32.dll
Import Module 017: comdlg32.dll
Import Module 018: shell32.dll
Import Module 019: ole32.dll
Import Module 020: cabinet.dll
Import Module 021: shell32.dll
Import Module 022: ace.dll ***
Import Module 023: Shell32.dll
Import Module 024: winmm.dll

Pour le dump on peut essayer avec ProcDump mais ca ne marche pas tjrs.
 
 
Enregistrement d'un utilisateur Oui mais OU ??

Après qq tests (plus ou moins longs en fonction du niveau du testeur ;)
On galère un peu dans AceTools.Dll (en fait il n'y a que les formulaires)
On passe dans Ace.Dll et on revient dans WinAce.exe etc...

Pour les nouveaux : Y a pas de secret on fait de façon classique :
Un bp sur un call et on le passe si Code non Valide alors il faut étudier ce call !
(on procède un peu par dichotomie)
 

// bien sur on peut être plus Zen et voir que ds Ace.Dll il y a des noms de fonctions
// qui sont très explicites donc on y met un BP ;) et hop Glop Glop !
 

Donc dans WinAce on tombe ici :
 
00558DBD FFD3 call ebx 
00558DBF 84C0 test al, al 
00558DC1 0F847B010000 je 00558F42 
00558DC7 33C0 xor eax, eax 
00558DC9 A348625800 mov dword ptr [00586248], eax
00558DCE 8D95F0FDFFFF lea edx, dword ptr [ebp+FFFFFDF0]
00558DD4 8D85F0FCFFFF lea eax, dword ptr [ebp+FFFFFCF0]
00558DDA E8459EEAFF call 00402C24
00558DDF BAA48F5500 mov edx, 00558FA4
00558DE4 8D85F0FCFFFF lea eax, dword ptr [ebp+FFFFFCF0]
00558DEA E8DD9DEAFF call 00402BCC 
00558DEF 8D95F0FCFFFF lea edx, dword ptr [ebp+FFFFFCF0]
00558DF5 B884625800 mov eax, 00586284
00558DFA B132 mov cl, 32 
00558DFC E83F9EEAFF call 00402C40
00558E01 8D45F0 lea eax, dword ptr [ebp-10]
00558E04 BA84625800 mov edx, 00586284
00558E09 E8CAB0EAFF call 00403ED8 
00558E0E 8D85ECFCFFFF lea eax, dword ptr [ebp+FFFFFCEC]
00558E14 8D95F0FEFFFF lea edx, dword ptr [ebp+FFFFFEF0]
00558E1A E8B9B0EAFF call 00403ED8 
00558E1F 8B85ECFCFFFF mov eax, dword ptr [ebp+FFFFFCEC]
00558E25 8D55F8 lea edx, dword ptr [ebp-08]
00558E28 E81BF9EAFF call 00408748 
00558E2D BB01000000 mov ebx, 00000001
00558E32 EB21 jmp 00558E55

00558E34 8B45F8 mov eax, dword ptr [ebp-08]
00558E37 8A4418FF mov al, byte ptr [eax+ebx-01]
00558E3B 2C20 sub al, 20
00558E3D 7404 je 00558E43
00558E3F 2C0D sub al, 0D
00558E41 7511 jne 00558E54
00558E43 8D45F8 lea eax, dword ptr [ebp-08]
00558E46 B901000000 mov ecx, 00000001
00558E4B 8BD3 mov edx, ebx
00558E4D E826B3EAFF call 00404178
00558E52 EB01 jmp 00558E55
00558E54 43 inc ebx
00558E55 8B45F8 mov eax, dword ptr [ebp-08]
00558E58 E8D7B0EAFF call 00403F34
00558E5D 3BD8 cmp ebx, eax
00558E5F 7ED3 jle 00558E34
00558E61 8D45F8 lea eax, dword ptr [ebp-08] 
00558E64 BAB08F5500 mov edx, 00558FB0
......
......
00558EB8 E83BB2EAFF call 004040F8 
edx=AVString
00558EBD 50 push eax
00558EBE E85992F5FF call 004B211C
00558EC3 8945F4 mov dword ptr [ebp-0C], eax
00558EC6 33C0 xor eax, eax
00558EC8 5A pop edx
00558EC9 59 pop ecx
00558ECA 59 pop ecx
00558ECB 648910 mov dword ptr fs[eax], edx
00558ECE EB0A jmp 00558EDA 
00558ED0 E907A6EAFF jmp 004034DC
00558ED5 E8A6A8EAFF call 00403780
00558EDA 837DF400 cmp dword ptr [ebp-0C], 00000000
00558EDE 7424 je 00558F04
00558EE0 C6058462580000 mov byte ptr [00586284], 00
00558EE7 8D95ECFCFFFF lea edx, dword ptr [ebp+FFFFFCEC]
00558EED A130EF5800 mov eax, dword ptr [0058EF30]
00558EF2 E83DC7EAFF call 00405634
00558EF7 8B85ECFCFFFF mov eax, dword ptr [ebp+FFFFFCEC]
00558EFD E8F2E0FDFF call 00536FF4
00558F02 EB3E jmp 00558F42
00558F04 833D4862580000 cmp dword ptr [00586248], 0000
00558F0B 0F95C2 setne dl
00558F0E 8B45FC mov eax, dword ptr [ebp-04]
00558F11 8B80F0160000 mov eax, dword ptr [eax+000016F0]
00558F17 E83433EDFF call 0042C250
00558F1C 6A00 push 00000000
00558F1E 8D95ECFCFFFF lea edx, dword ptr [ebp+FFFFFCEC]
00558F24 A118F35800 mov eax, dword ptr [0058F318]
00558F29 E806C7EAFF call 00405634
00558F2E 8B85ECFCFFFF mov eax, dword ptr [ebp+FFFFFCEC]
00558F34 668B0DB48F5500 mov cx, word ptr [00558FB4]
00558F3B B202 mov dl, 02
00558F3D E89EBFEFFF call 00454EE0
00558F42 33C0 xor eax, eax
00558F44 5A pop edx
00558F45 59 pop ecx
00558F46 59 pop ecx
00558F47 648910 mov dword ptr fs[eax], edx
00558F4A 68728F5500 push 00558F72
00558F4F 8D85ECFCFFFF lea eax, dword ptr [ebp+FFFFFCEC]
00558F55 E85EADEAFF call 00403CB8
00558F5A 8D45F0 lea eax, dword ptr [ebp-10]
00558F5D E856ADEAFF call 00403CB8
00558F62 8D45F8 lea eax, dword ptr [ebp-08]
00558F65 E84EADEAFF call 00403CB8
00558F6A C3 ret

Appel du TRegForm (saisie du code)
Bouton Cancel ?
Oui on annule...
Sinon on test les résultats
 
 
 
 
 

Taille de la AVString (UserName)
 

On transforme la chaîne en une 0Ended
 
 

edx = Code
 

*eax = Code
 

eax = 17h = length(Code)
 

Nettoyage du code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

eax = code ss les '-'
 
 

eax=Code 
 

Renvoie une erreur de code!!
 
 
 
 
 

RegUser OK si [00586248]=0
 

OK si [00586248]=0
 
 
 
 
 
 
 

OK si [00586248]=0
 
 
 
 
 
 
 
 
 
 

Affiche Reg OK
 
 
 
 
 
 
 
 
 
 
 

 


 

On voit donc que l'on peut forcer l'enregistrement de l'utilisateur à Vrai !
MAIS MAIS : cela ne ce fait pas complètement car il y a un KeyFile (Ace.KEY)
qui n'est pas écrit...

Pourrait-on forcer cette écriture ? ;-)
Pourquoi ne pas simplement chercher le flag qui dit Reg OK et le fixé ?
 
On étudie de plus près : call 004B211C

Et l'on fini par arriver dans Ace.Dll avec un décalage de 00250000 à ajouter
(en fct de l'adresse de chargement)

On a un JMP [005B9B74] = JMP 0066241C
 
Exported Function : ACERegister Vu le nom de la fct on ne pouvait pas se tromper !

:0041241C 56 push esi
......
......
:00412438 83F913 cmp ecx, 00000013 ::: si length(code) < 13h alors
:0041243B 740A je 00412447
:0041243D B821020000 mov eax, 00000221 ::: ERREUR 221 (mauvais code)
:00412442 E94EFBFFFF jmp 00411F95
::: sinon
:00412447 8B7510 mov esi, dword ptr [ebp+10] ::: esi = Code
:0041244A BFCEF04400 mov edi, 0044F0CE
:0041244F 57 push edi
:00412450 AC lodsb
:00412451 8807 mov byte ptr [edi], al
:00412453 47 inc edi
:00412454 3C00 cmp al, 00
:00412456 75F8 jne 00412450
:00412458 5F pop edi :::fin de copie du code ds Edi
:00412459 BE1CDF4400 mov esi, 0044DF1C ::: var globales
:0041245E BFD1DC4400 mov edi, 0044DCD1
:00412463 E80E78FFFF call 00409C76 *** Test du Code
:00412468 57 push edi
:00412469 AC lodsb
:0041246A 8807 mov byte ptr [edi], al
:0041246C 47 inc edi
:0041246D 3C00 cmp al, 00 ::: al = 0 si code OK
:0041246F 75F8 jne 00412469 ::: sinon affiche Code not good
:00412471 5F pop edi
:00412472 A114DF4400 mov eax, dword ptr [0044DF14]
:00412477 E90FFBFFFF jmp 00411F8B


 
On étudie de plus près : call 00409C76

 
:00409C76 51 push ecx
:00409C77 52 push edx
:00409C78 56 push esi
:00409C79 57 push edi
:00409C7A E8F5FCFFFF call 00409974 :::test du code
:00409C7F 833D9813440000 cmp dword ptr [00441398], 00000000
:00409C86 0F85BD000000 jne 00409D49 ::: ERREUR 221
:00409C8C 833D86F0440000 cmp dword ptr [0044F086], 00000000
:00409C93 742D je 00409CC2
. . . .
. . . .
:00409CE3 C70598134400FF000000 mov dword ptr [00441398], 000000FF
:00409CED 5F pop edi
:00409CEE 5E pop esi
:00409CEF 5A pop edx
:00409CF0 59 pop ecx
:00409CF1 C3 ret

 
On étudie de plus près : call 00409974

C'est là que l'on s'amuse !
 
:00409974 53 push ebx
......
:00409979 C8200000 enter 0020, 00 :::un peu de mémoire locale
:0040997D 8D7DF0 lea edi, dword ptr [ebp-10]
:00409980 BE28104000 mov esi, 00401028
:00409985 A5 movsd
:00409986 A5 movsd
:00409987 A5 movsd
:00409988 A5 movsd
:00409989 B910000000 mov ecx, 00000010
:0040998E BED1F04400 mov esi, 0044F0D1
:00409993 8D7DE0 lea edi, dword ptr [ebp-20] :::esi= code - les 3 1er lettres
:00409996 31C0 xor eax, eax
:00409998 57 push edi
:00409999 F2 repnz
:0040999A A4 movsb
:0040999B 5F pop edi :::fin de copie ds edi en 008FF830
:0040999C 8B0D38AB4200 mov ecx, dword ptr [0042AB38] :::"34679ACDEFHKLMNPQRSTWXY"
:004099A2 EB06 jmp 004099AA ::: appelons cette chaîne GrpA
......
:004099AC EB06 jmp 004099B4
......
:004099B4 8A5C28E0 mov bl, byte ptr [eax+ebp-20] :::bl = 1ere lettre
......
On vérifie si les lettres du Code sont dans le GrpA :
"34679ACDEFHKLMNPQRSTWXY" sinon il y a une erreur : 
Donc le code ne peut être composé que de ces lettres là!
......
:004099C1 83FA17 cmp edx, 00000017 ::: bl pas ds GrpA
:004099C4 75DE jne 004099A4
:004099C6 C7059813440021020000 mov dword ptr [00441398], 00000221 ::: ERREUR 221
:004099D0 E950360000 jmp 0040D025 ::: bad Code
.......
si OK on se retrouve avec edi qui pointe sur la liste des index des lettres du code
par rapport à la position ds GrpA (exp : A ds le code donne 05 ds la zone ptée par edi)
.......
Qq calculs sur le code ainsi modifié
.......
:004099E3 6B442AF017 imul eax, dword ptr [edx+ebp-10], 00000017
:004099E8 83C204 add edx, 00000004
:004099EB 89442AEC mov dword ptr [edx+ebp-14], eax
:004099EF 83FA0C cmp edx, 0000000C
:004099F2 75EF jne 004099E3
.......
Qq calculs sur le code
.......
:00409A3E 7407 je 00409A47
:00409A40 B801000000 mov eax, 00000001
:00409A45 EB02 jmp 00409A49
:00409A47 31D8 xor eax, ebx
:00409A49 BB03000000 mov ebx, 00000003 :::compteur pour un calcul
:00409A4E BACEF04400 mov edx, 0044F0CE :::adrs mémoire
:00409A53 A386F04400 mov dword ptr [0044F086], eax :::init
:00409A58 B8FFFFFFFF mov eax, FFFFFFFF :::init
:00409A5D E87C580000 call 0040F2DE :::Calcul de CRC??
:00409A62 0FB6C0 movzx eax, al :::on garde AL (doit être = à C3)
:00409A65 A38AF04400 mov dword ptr [0044F08A], eax :::on sauve en mémoire
:00409A6A 89C2 mov edx, eax
:00409A6C B8E2F04400 mov eax, 0044F0E2 :::Adresse
:00409A71 E891DC0000 call 00417707 :::Calcul de CRC??
:00409A76 E9AA350000 jmp 0040D025 :::on s'en va!!! enfin

Donc tout ca, est-ce bien le calcul de vérif code !! // Non il en manque encore bcp !
Quel B....L de M...E !!!
Ca multiplie, ca décale, ca tourne, ca appel et allons-y ne nous privons pas c'est du Pascal !
Mais pensez donc à ceux qui regarde ca sous un autre œil !
En assembleur c'est moins amusant ce genre de chose !
 

Là il y a un choix à faire :

Soit il y a une routine qui calcule le bon code soit on utilise tjrs cette routine pour vérifier.
Qd le prog se lance il doit vérifier si l'on est inscrit donc si l'on passe ds le coin au démarrage
c'est qu'il y a vérification du code saisie (donc stockage qq part - ds Ace.key pour ceux qui on bien lu)...
On va qd même vérifier en regardant un peu le code source du prog et des Dlls (Ace.dll)...
A plus tard...
 

Bon comme on a trouver la routine de calcul c'est plus simple mais on se balade d'un call à l'autre en
dead listing c'est dur dur.
 

Là on arrête tout et on fait une pause kitkat !
(qq mn plus tard...)

Soyons plus Zen et réfléchissons un peu :
Données du pb => une AVstring (username) et un code
Solutions possibles => (il n'y en a pas 36)
1- le username donne un code mémoire qui est comparé à celui saisie ?
2- le username donne un CRC qui est comparé à celui du code saisie ?

Soluce 1: le code fishing => il doit y avoir ds le prog une comparaison de chaîne
Soluce 2: on fait un (ou plusieurs) calcul(s) avec le username et
un (ou plusieurs) calcul(s) avec le code ensuite on compare le résultat (généralement un entier)...
On a vu en 00409974 que le 1er calcul est fait sur le code donc on peut
imaginer qu'il s'agit d'un calcul de CRC !

On remarque ds le code (:00409A2D et :00409A6C) que le résultat est placé en 0044F0E2.
Donc on va utiliser une vieille ruse de Sioux : suivre le gibier à la trace !
On passe donc sous debuger et on surveille la zone mémoire 0044F0E0:0044F0F0
On prend une zone un peu plus large pour les 1ers tests...
Petite promenade...Remarque importante on lit/écrit aussi en 0044F080:0044F090 donc
on regarde aussi cette zone mémoire...
Résultats:
1°) Calcul de CRC avec le code, écriture en 0044F0E2:0044F0E8 (adrs:00409a2d)
2°) Copie du résultat en 008ff4bc (adrs:00417794)
3°) Copie d'une valeur calculée de 008ff6fc vers 0044F0E2 (adrs:004177ab)
4°) Calcul de CRC avec la AVString (username), écriture en 0044F082 (adrs:00409c45)
5°) Test important 0044F082=0044F0E2 ??? OUI alors le code est bon !!!
On sait que :0040999C nous indique que les lettres composant le code sont ds
"34679ACDEFHKLMNPQRSTWXY"

Un indice on remarque que le CRC calculé en 0040f2de des 3 1eres lettres du code
doit faire eax=??????C3. Un peu de Zen et on se dit que comme ds tous les progs
il doit s'agir des initiales du prog. On fait un test avec "ACE" et c'est gagner!!!
Il devient maintenant très simple de patcher le code du programme (en mémoire pour tester)
en regardant la zone mémoire de 0044F080:0044F0EF et en répondant aux interrogations du prog !

qqs exemples mais pas tous : il faut aussi que vous travailliez un peu
 
:00409C7F 833D9813440000 cmp dword ptr [00441398], 00000000
:00409C86 0F85BD000000 jne 00409D49 ::: devient EB049090
:00409C8C 833D86F0440000 cmp dword ptr [0044F086], 00000000
:00409C93 742D je 00409CC2 ::: devient EB2D
:00409C3D E89C560000 call 0040F2DE
:00409C42 8B55BC mov edx, dword ptr [ebp-44]
:00409C45 8902 mov dword ptr [edx], eax
:00409C47 3B4260 cmp eax, dword ptr [edx+60]
:00409C4A 7521 jne 00409C6D ::: devient 9090
:00409C59 E880560000 call 0040F2DE
:00409C5E 8B55BC mov edx, dword ptr [ebp-44]
:00409C61 3A4266 cmp al, byte ptr [edx+66]
:00409C64 7507 jne 00409C6D ::: devient 9090

etc... il y a en tout 8 adresses et 17 octets à changer........


PETIT COMPLEMENT (pour les puristes du patch ;o)

On peut réduire cela à 2 octets en modifiant deux "jmp si EAX=1"... ;o))
En fait la protection vérifie 2 fois que le CRC du Code est égal au CRC de l'AVString (userName).
Donc en forcant le passage à 2 endroits différents on obtient un code valide et un fichier clef.
Une autre méthode : (relative au point 5°)
S'arranger pour que la routine qui compare les CRCs renvoie toujours EAX=1 !
Attention : cela peut-être dangereux si le prog utilise la routine de CRC pour valider certaines archives ZIP...

Pourquoi ne pas l'avoir dit plutôt ??

Simplement parceque le but de l'étude est un KeyGen, donc je n'ai pas cherché à améliorer le patch du prog.
Si votre but est juste de vous enregistrer, avec un debugger et 1 break point sur Ace.Dll vous pouvez
générer un keyFile. Ensuite vous utilisez le prog de façon normale. Il n'y a aucun problème...
Donc une suggestion pour le programmeur : vérifier la validité des valeurs à chq chargement.
Pour que l'on soit au moins obligé de faire un Patch du prog ;)


Ceux qui se demandent comment on fait pour savoir si l'on "jump" ou pas doivent faire un peu plus de "dead listing". On y remarque très bien si l'on part vers une erreur ou un affichage de msgbox
(sinon on test et on prend des notes ;o)

Ensuite on saisie un Username et un code "ACEDT....DT" et on s'enregistre...Bingo !!
(en fait on doit prendre des lettres ds : "34679ACDEFHKLMNPQRSTWXY"
Parce Que ca ne sert à rien de mettre des patchs ds tous les coins -
sinon on peut aussi patcher ce test ?-).
Le fichier Ace.Key est généré et l'utilisateur est le bon et on peut en changer qd on veut !

Tous ceci serait très sympa mais d'après vous : Pourquoi avoir Aspacker l'exe et laisser les Dlls en clair ???
Réponse : Tout simplement car il y a un test de validité de la Dll dans le prog !!!
Donc on peut tjrs utiliser un patcher mémoire ou trouver la routine de contrôle et la modifier (tjrs patch mémoire)
on peut aussi garder le prog unAspaked (on l'a fait au début) et le patcher en direct !
IL Y A AUSSI UNE AUTRE SOLUTION : (moins élégante)
Une version de la Dll patchée et une non patchée. On utilise une fois la Dll patchée pour l'enregistrement
ensuite on utilise la classique. Comme ca pas besoin de unAspack ni de patcheur mémoire...

Mais on doit pouvoir faire mieux !
 
 
Etudions la routine de contrôle de la Dll

 

En première réflexion on pense à un calcul de CRC sur la Dll (un octet de modifié et hop perdu !!)
Ensuite on peut imaginer qu'il y a un test des octets cruciaux (un octet de modifié et hop perdu !!)
Comment le savoir me direz-vous : Très simple on prend la Dll et on modifie un octet très en dehors des "zones sensibles"... Si un message s'affiche => c'est un CRC sur le fichier complet !!
Résultat : On cherche un texte ds la Dll et on le modifie => ERREUR de Dll !!!
On a donc un CRC sur toute la Dll. On ne peut donc rien modifier même en équilibrant le poids des octets.
Il y a d'autre possibilité de tester le code :
- somme des octets (contournable par équilibrage du code)
- somme des octets pondérée (octet1*1 + ... + octet9*9 + octet10*1 + ... + octet19*9)
- Vérification du nombre de NOP (Au lieu de mettre 2 NOP on met un DEC + un INC)
- Etc...
Une chose à faire si vous ne maîtrisez pas les CRC : aller sur le Web et trouver des codes sources de routines de calcul de CRC (freeWare). Vous apprendrez comment en 2 ou 3 ligne de prog on fait une solide vérification. Vous remarquerez qu'il y a un tableau d'entiers 32 bits qui est utilisé (intéressant à chercher avec un Editeur hexa). Vous apprendrez que l'entier recevant le résultat est initialisé à FFFFFFFF au début du calcul. Ensuite vous compilez le code source et vous en étudiez l'assembleur cela vous donne une idée du type d'instructions utilisées (shr résult..xor index..shl index..xor résu,tableau[index])...
Juste avec ces infos vous pouvez trouver la routine de calcul ds le prog ;o))
Calculons les CRCs de la Dll correcte =>
E0/chk8 22E0/chck16 0092/crc16 56BF/ccitt DEC8F3A0/crc32 (valeur en complément à 1)
Pour ceux qui ne manient pas les calculs : HexWorkShop fait ca très bien !
ATTENTION : Suivant les algos on trouve des valeurs à +/- 1.

On gardera en mémoire la valeur D3C8F3A0 qui est un CRC32 (bien ds le style du prog)

Là il y a plusieurs voies possibles :
- recherche en dead listing d'une routine de calcul de CRC (facile)
- recherche en dead listing d'un test sur la valeur du CRC (il faut de la chance)
- live Debug et recherche de lecture du fichier (ace.dll)

Supposition : Au feeling on va dire qu'il y a ouverture du fichier, lecture et comptage...
Donc cherchons les ouvertures de fichiers.
Plaçons les pts d'arrêts sur : CreateFileA, ReadFile, LoadLibraryA
(pour faire la différence avec le chargement de Ace.Dll)
INFO: Avec ces routines le nom du file sera ds EDI.

On tombe sur :
1°) CreateFileA pour Ace.Dll
2°) ReadFile pour Ace.Dll ( bon début )
3°) On trace en pas à pas ( on passe qd même au dessus des routines )
On regarde les Registres..
4°) Tient en retour de fonction EAX=DEC8F3A0 !!!!!! Ca nous dit qq chose cette valeur.
(On vient donc de passer la routine de calcul du CRC)
5°) On continue en prenant des notes sur les adresses d'exécution...
6°) Tient cmp EAX, DEC8F3A0 !!!! Joli test de CRC valide
si OK alors EAX=0 et Ret
7°) He hop : test AL, AL si OK alors goto somewhere...

Voilà, voilà : Donc plusieurs possibilités =>
1°) On remplace la valeur DEC8F3A0 par celle du CRC32 de la DLL modifiée
2°) On force la comparaison par un mov EAX,00000000
3°) On force le test AL par un Jump SomeWhere
4°) On force une comparaison avec le fichier de la Dll non modifiée
 
 
Etudions la façon de placer les modifications

 

1°) On change ds la Dll (pas de problème particuliers)
2°a) On patch l'exe décompressé simple et efficace
2°b) On ajoute du code au prog compressé pour faire un patch mémoire de l'exe !

Regardons cette solution 2°b :

Où, quand, comment et pourquoi ????

1°) OU : Simplement là où il y a de la place ds le code ;o)
On va prendre le PE header du prog compressé (ProcDump, Hiew...) et l'on regarde les sections.
La plus sympa est .aspack on voit qu'elle a une taille physique < à la taille virtuelle (alignement de code)
donc on se dit qu'il y a de la place par là.
Pour en être sur on regarde et l'on voit que le prog ne passe pas 00649BFF en adresse
alors que l'on voulait justement écrire entre 00649C000 et 00649FFF ! ! !
(Adrs = Image (00400000) + VirtualOffset (00248000) + PhysiqSize (1C000) = 00649C000)
Mais on remarque qu'il y a pas mal de 00 après 00649B11 donc un petit test en lecture/écriture
mémoire va nous dire si le programme se sert de cette zone mémoire.
Réponse NON donc on peut utiliser cet espace pour mettre du code machine !

2°) QUAND : Simplement quand le compacteur a fini son travail ;o)
Nous avons vu au départ (lors de la phase de Dump) que la fin du travail se situe en 006484CC
en remontant un peu on voit MOV [adrs], EAX voilà un bel endroit pour détourner l'attention du programme
et le modifier qq peu (car EAX contient le point d'entré du prog décompressé).
 
006484B3 5B POP EBX
006484B4 0BDB OR EBX,EBX
006484B6 89855C2E4400 MOV [EBP+00442E5C],EAX (EAX=EntryPt)
006484BC 61 POPAD
006484BD 7508 JNZ 006484C7
006484BF B801000000 MOV EAX,00000001
006484C4 C20C00 RET 000C
006484C7 6800000000 PUSH 00000000 (le pt d'entré est placé ici)

3°) COMMENT : Bonne question ;o)
En 006484B6 on va faire un JUMP 00649B20 et à cette adresse nous allons placer
le code manquant pour une bonne étude du programme...
 
006484B6 JMP 00649B20 : Adresse du patch
...
00649B20 MOV BYTE PTR [Adrs1],EB
00649B27 MOV BYTE PTR [Adrs2],EB
00649B2E MOV [EBP+00442E5C],EAX on écrit le point d'entré;
00649B35 JMP 006484BC On retourne d'où on vient...

He voilà on lance le prog original (un peu modifié) et tout va bien on peut s'enregistrer,
changer qd on veut de nom utilisateur et bénéficier de l'ensemble des fonctions...
 
 
Voilà donc les secrets de la protection de WinAce 8-)
Bon prog, tout n'est pas l'un en dessous de l'autre, 
on a une bonne segmentation du code qui balade bien le cracker de base...
Le checksum et le prog Aspaked compliquent un peu les choses...
Une bonne surprise pour un shareWare bien sympa !
- MAIS comme tjrs avec un peu de Zen on arrive au bout !
Pour ceux qui se demandent comment être ZEN ?
Réponse : Faite une pose de temps à autre, ayez l'esprit clair...
Ensuite devant un problème particulier demandez-vous comment vous auriez
programmé cela. Si vous êtes bon le prog sera certainement comme vous le pensez !

Pour la prochaine fois : LE KEYGEN
Le générateur de clef de WinAce n'est pas direct cad qu'il ne s'agit pas d'une fonction qui calcul la clef pour en faire une comparaison. En fait on calcul le CRC32 du nom utilisateur (AVString) et on le compare avec une valeur calculée dans diverses routines à partir de constantes générées par le programme.

Pour aider ceux qui veulent faire qq recherches : 
ds la dll :
  004175BD => esi = adresse ou sera placé le code calculé
  00417695 => Routine de calcule des constantes (de 006B242B à 006B34ED)
  0041748C => Ecriture du code lors d'une boucle de calcul...
Bon courage...

Pour ceux qui se demandent pourquoi il n'y a pas toutes les adresses à patcher ?
Réponse : Le tutorial est fait pour apprendre pas pour faire un crack en direct !
Avec ce tut pour faire un crack il vous faudra travailler un peu, vous avez le chemin
à suivre, la technique à utiliser, il ne vous reste plus qu'à pratiquer !
 

PAS DE SITE PAS D'ADRESSE EMAIL :
Question ? => posez-les sur les forums français...


 

Comme toujours : 

VOUS NE DEVEZ PAS UTILISER CE COURS POUR OBTENIR ILLEGALEMENT DES PROGRAMMES.
IL DOIT JUSTE SERVIR A VOTRE EDUCATION.
AVOIR CE COURS EST LEGAL MAIS L'APPLIQUER NE L'EST PAS !!!
 


 
 
 
CODE SOURCE (Dump mémoire style Sice vers fichier binaire)

(non optimisé mais qui fonctionne bien...)
 
procedure Raw2Bin;
var fi : textfile; fo : file; barray : array[1..16] of byte;
ph : string; i,j : integer;

function hextobyte(a,b : char) : byte;
var ai, bi : byte;
begin
if (a>#$39) then ai := byte(a)-55 else ai := byte(a)-48;
if (b>#$39) then bi := byte(b)-55 else bi := byte(b)-48;
result := 16*ai + bi;
end;

begin
// il faudrait vérifier que le fichier source existe...
// if "ca existe" then
// begin
// ouverture des fichiers
assignFile(fi, 'fichier-dump mémoire style Sice'); //mettre le bon nom
reset(fi);
assignFile(fo, 'fichier_binaire.bin'); //mettre le bon nom
rewrite(fo,1);
// conversion de ligne du type :
// 0030:005B9BD0 6B 65 72 6E 65 6C 33 32-2E 64 6C 6C 00 00 00 00 kernel32.dll..
// du caractere 15, 2 par 2 , 16 fois
while not eof(fi) do
begin
readln(fi, ph); // lecture ligne texte
j := 15;
for i:= 1 to 16 do // conversion
begin
barray[i] := hextobyte(ph[j],ph[succ(j)]);
inc(j,3);
end;
BlockWrite(fo, barray, 16);
end;
//fermeture
closeFile(fo);
closeFile(fi);
//end;
end;