TUTORIAL sur Laserlock v5.00.06 (le jeu Desperados : Wanted Dead Or Alive) :

Protection commerciale : Laserlock v5.00.06 (http://www.laserlock.com/)
Editeur : Infogrames
Date de sortie : FR 27 avril 2001
http://www.jeuxvideo.com/articles/0000/00001354_test.htm
Site officiel : http://www.spellbound.de/web/en/sb.php?m0=p_d1&r0=p_d1r&id=10&id2=1
Niveau requis en cracking : [x] facile    [x] intermédiaire    [ ] confirmé    [ ] expert
Il est préférable d'avoir déjà cracké des protections de jeu de type Safedisc / SecuROM avant de lire ce tutorial (+ des connaissances sur le PE, manual unpacking et imports rebuilding).

Outils nécessaires :
Softice 4.01
Icedump
Procdump / LordPE
Hiew / Hex Workshop
W32Dasm
Adump
Masm v8

Vous devez posséder le jeu original ou un clone fonctionnel (il y a un profil Laserlock dans Alcohol 120%) pour pouvoir appliquer la 1ère partie de ce tutorial...
Il est quand même possible de cracker cette protection sans le CD ;-)
De plus, il s'agit de la version française du jeu Desperados : Wanted Dead Or Alive...

OS sur laquelle je travaille: Win98 SE.
Ce tutoriel est aisément transposable sur d'autres debuggers (OllyDbg, par exemple) et d'autres OS.

Note : Je sais qu'il existe un très bon tutorial sur Laserlock v5 (Messiah) par GáDiX & SkUaTeR, mais il est en espagnol et ceci n'est pas juste une traduction :p

Ce tutorial est détaillé de façon suivante :

A) Introduction et généralités
B) Fixer les call Laserlock
C) Restaurer l'Import Table
D) Perspectives et conclusion
E) Remerciements

A) Introduction et généralités :

Laserlock est une protection commerciale créée par MLS LaserLock INC.
Elle est caractérisé par un dossier Laserlok à la racine du CD.



Il contient un fichier laserlok.in, également caché...

La protection repose sur ce fichier "incopiable" (en tout cas de façon conventionnelle).
Elle n'a plus qu'à vérifier la présence de ce fichier pour déterminer s'il s'agit d'une copie ou non.

Pourquoi Laserlok ?

The actual name of the protection scheme is Laserlock, as stated on the companie's website. By searching the source CD a hidden directory called "LaserLok"
can be found. The directory uses the standard DOS 8.3, hence the exclusion of C from the name.

Merci à mars pour la précision :).

Protections mises en place :

Vérification de l'intégrité de l'exécutable et de la .dll protégés (checksums).
Cryptage partiel de la section .text de gamedll.dll (l'exécutable, lui n'est pas packé !).
Redirection au niveau des APIs de game.exe (call API transformés en call Laserlock).
Pas d'anti-debugging.

Les différentes étapes de la protection :

1. Chargement de game.dll (crypté par parties)
2. Détournement des call APIs vers gamedll.dll pour des vérifications d'intégrités de game.exe et gamedll.dll
Si Laserlock (gamedll.dll) détecte un changement, il retourne une mauvaise adresse de l'API détournée.
3. Toujours via gamedll.dll (et donc via le détournement des APIs), s'affiche le logo :



4. Puis on a la protection proprement dite (lecture de la structure physique du CD, mise en place par la protection), toujours via la .dll de Laserlock.

Si l'authentification du CD a échoué, on a la boîte suivante :



5. Les APIs sont détournées au fur et à mesure, et à chaque fois, Laserlock effectue un/des checksum(s) sur gamedll.dll (notamment sur la routine, qui calcule et retourne la bonne adresse de l'API détournée).
6. A partir d'un certain moment, Laserlock remplace les call Laserlock par les call APIs (comme cela était le cas, avant d'appliquer la protection), pour un gain de performances...

B) Fixer les call Laserlock :

On regarde le PE à l'aide du PE Editor de Procdump : rien d'anormal.
On a aucun problème à désassembler game.exe avec W32Dasm.
On a toutes les String References...
Le fichier n'est donc pas packé.
Par contre, on n'a pas d'informations sur les imports...
Si l'on regarde au niveau de l'OEP, on a ceci :

On s'aperçoit déjà que les call API sont tous remplacés par des call dword ptr [0067A2A0].
Il s'agit donc d'une redirection des APIs.
(On les appelera les "call Laserlock").

On lance donc SoftIce pour voir ce qu'il y a, à l'intérieur...
Tiens, on arrive dans gamedll.dll...
La protection utilise donc une .dll pour la redirection des APIs.

Après le jmp [10017A68], et traçant quelques nop, on arrive finalement à la routine "Laserlock", chargée de calculer l'adresse de l'API à effectuer...

Toujours avec l'exemple du call dword ptr [0067A2A0] en 006201E4, on va tracer cette routine en step over (F10). Une fois arrivé en 100025B1, on exécute le ret pour finalement arriver dans l'API GetVersion (Kernel) situé à l'adresse BFF92F1B... (adresses, qui varient selon la version de l'OS).
Un petit F12 nous ramène juste après notre call Laserlock de l'adresse 006201E4.
Cherchons un peu pour voir si cette adresse ne serait pas dans l'IAT de game.exe, par un s 0 l FFFFFFFF 1B,2F,F9,BF.
On trouve alors une occurence en 00652198 (section .rdata).

On doit donc remplacer call Laserlock par call API...
Ici, il faut remplacer FF15A0A26700 par FF1598216500.
e 006201E6 98,21,65,00.
Et voila, on a fixé un call.

Il nous reste plus qu'à automatiser ce que l'on vient de faire manuellement par une petite routine (un "call fixer").
Mais avant, il faudrait comprendre le JZ 100025B2 en 100025A0 de la routine précédente.
Pour cela, mettons un bpx en 100025B2 (bp sous Ollydbg), puisque le saut n'est effectué que très rarement...

On relance (F5), vérification du CD...
Et ça crashe...
On se retrouve à une adresse farfelue (ABFABFF7) après l'exécution d'un call Laserlock (ici 62725E) au lieu de breaker. Laserlock ne doit donc pas aimer les modifications ; il nous fait sauter ailleurs et non au niveau l'API normalement prévue...
Bon on va essayer un bpm 100025B2 X, à la place (hardware breakpoint).
On breake et en regardant dans la pile, on trouve comme adresse 0062B44E (pour le call Laserlock, répondant à cette condition).
Il ne reste plus qu'à relancer et à mettre un bpm à cette adresse.

Avant d'exécuter le call Laserlock en 0062B44E,   esp=0099FD68
En rentrant à l'intérieur de celui-ci (step into = F8), esp=0099FD64
Arrivé au ret de l'adresse 100025C0,                    esp=0099FD64
En l'effectuant, on arrive dans l'API avec                esp=0099FD68
Le ret de l'API nous amène enfin en 0062B28A et non en 0062B454 comme il devrait, s'il s'agissait bien d'un call API classique...
Le call Laserlock en 0062B44E correspond donc à un jmp API et non à un call API, ce qui explique la présence du saut conditionnel dans la routine Laserlock...

Maintenant, on peut écrire notre "Call-fixer" :)

Ce que fait notre routine :
Au démarrage, elle va patcher gamedll.dll en 10002596 par un jmp edi pour détourner vers notre Call-fixer.
On récupère ensuite l'adresse de notre Call-fixer, chargé dans Adump (plus particulièrement la routine here1), pour la stocker dans edi.
Search_loop recherche toutes les occurences du call Laserlock dans le code de game.exe (section .text).
Une fois un call Laserlock repéré, on va l'émuler pour faire croire que le jeu s'exécute normalement (et permettre au programme de
retrouver l'API par rapport à l'adresse poussée sur la pile)... (Les occurences sont stockées en edx).
On saute au début de la routine Laserlock par un jmp dword ptr [67A2A0].
Le jmp edi en 10002596, nous permet de récupérer l'adresse de l'API.
Il saute en here1. L'adresse de l'API est stockée en ebp+08h, par Laserlock.
Il ne reste plus qu'à se servir...
Ensuite on détermine si le call Laserlock correspond à un call API ou à un jmp API.
On reprend tout simplement l'instruction de la routine Laserlock, càd le cmp byte ptr [10017A18], 01.
Si l'on a à faire à un jmp API, on remplace l'opcode FF15 par FF25 (correspondant à un jmp [API]).
Ensuite, on donne la bonne valeur à eax pour retourner à notre routine après le ret de la routine Laserlock.
Pour un call API, il faut retourner en here2.
Pour un jmp API, il faut retourner en here2+1, pour arriver au popad (et donc éviter le pop eax).
Puis l'on saute en _RoutinePatch+03 pour laisser continuer le programme.
Au 1er ret rencontré, on retourne alors en here2.
Le try_again permet la recherche d'autres occurences jusqu'à ce que l'on arrive à la fin de la section .text .

Call-fixer (desperados.asm) :


title call_fixer
.386
.model small, stdcall
option casemap :none
     
.code
    _TextOffset             equ 00401000h
    _TextSize                equ 00251000h
    _RoutinePatch         equ 10002596h
    _RoutineCompare   equ 10017A18h

start:
    pushad
    mov esi,_RoutinePatch
    mov word ptr [esi], 0E7FFh
    call @1
@1:
    pop edi
    add edi, offset here1-offset @1
    mov edx, _TextOffset
    mov ecx, _TextSize
   
search_loop:
    cmp word ptr [edx], 15FFh
    jne try_again
    cmp dword ptr [edx+2], 0067A2A0h
    jne try_again
    lea eax, [edx+6]
    pushad
    push eax
    ;jmp dword ptr [67A2A0]
    db 0FFh,25h,0A0h,0A2h,67h,0
here1:
    mov edx, dword ptr [ebp+08h]
    mov dword ptr [edx-4], eax
    mov eax, edi
    mov ebx, _RoutineCompare
    cmp byte ptr [ebx], 01
    jne @2
    mov byte ptr [edx-5], 25h
    inc eax
@2: add eax, offset here2-offset here1
    mov esi, _RoutinePatch+03
    jmp esi
here2:
    pop eax
    popad
try_again:
    inc edx
    dec ecx
    jne search_loop
    popad
    int 03
end start

En tapant r dans adump, on obtient l'adresse où l'on peut charger notre routine...
On assemble et on charge cette routine en mémoire par la commande l d'adump.

Note : Adump était juste un utilitaire, qui allouait de la mémoire afin d'y charger des routines (ou n'importe quoi), sous Softice.
Sous OllyDbg, il suffit de copier cette routine juste après le PE. On peut également allouer de la mémoire, dédiée à cette tâche sous OllyDbg (Alt+M pour avoir le Memory map, suivi d'un click droit > Allocate Memory).

On breake donc à l'OEP de game.exe et l'on modifie l'eip pour exécuter notre routine.

On ne peut pas terminer notre routine par un int 03 et l'arrêter par la commande i3here on, puisque dans ce jeu, il y a interférence de l'int 03 avec l'exécution de Nomouse.com .
Du coup, on mettra une boucle infinie à la place. Plus simplement, on peut mettre un bpx dans Adump...
Une fois la routine terminée, on retourne à l'OEP, par un r eip OEP.
On lance et malheur, ça crashe lamentablement...

Ah mais oui...
On a modifié gamedll.dll et cela n'a pas plu à Laserlock.
Du coup, il ne nous a pas retourné les bonnes valeurs...

Un petit bpr 10002596 10002598 R (memory breakpoint) nous amène à cette routine :




[ebp-08] correspond au compteur (position dans le bloc à vérifier).
[ebp+0C] stocke la taille du bloc.
[ebp+08] stocke le début du bloc.
[ebp-04] stocke le résultat du checksum...

La comparaison en 1000A2FA détermine si la vérification du block est terminée (compteur = taille du block ?).
L'instruction en 1000A331 permet de lire le block byte par byte. Ensuite, eax (résultat du checksum), auquel un AND EAX, 0000FFFF a préalablement été effectué, est ajouté à dl pour être stocké en [ebp-04]. Le compteur est incrémenté et ainsi de suite...

Cette routine est exécutée x fois (x étant assez grand pour nous décourager à patcher)...
En plus, cette routine ne vérifie pas exactement la même partie de code de gamedll.dll à chaque appel.
Donc il faut penser à une autre technique plus subtile qu'un patch au niveau de la routine Laserlock...

Comme l'a suggéré GáDiX, il est possible de contourner la difficulté avec un bpm et une petite macro :
BPM 10002596 X do "r eip here1;g;"
en remplaçant here1 par la bonne valeur, càd son adresse dans Adump.
Sous Ollydbg, il sera plus simple d'utiliser un script dans ce cas là.

On réessaye avec la même routine, mais sans patcher le code de Laserlock (par un jmp edi) et en tapant la macro précédente.
La routine étant terminée, on retourne à l'OEP, par un r eip OEP.
On lance et boom : un nouveau crash, avec des call toujours mal fixés...
Qu'est-ce que c'est que ça ?
On n'a pourtant rien modifié dans la routine Laserlock !!!
Si l'on n'a rien modifié dans gamedll.dll, on modifie bien au fur et à mesure game.exe .
Or une routine dans gamedll.dll vérifie également si l'on n'a rien modifié dans le code de game.exe :




Il s'agit d'un checksum effectué sur la section .text, sur le bloc commençant en 401000 et de taille 25009.
La routine est quasiment identique à la précédente à ceci près qu'il n'y a pas de AND EAX, 0000FFFF exécuté sur le résultat du checksum avant l'ajout byte par byte...
Mais cette fois, on n'a plus de chance :)
Elle n'est exécutée que quatre fois (lorsque le logo est affiché).
Il suffit donc d'attendre la 4ème vérification pour ensuite exécuter notre "call-fixer".
On peut réperer visuellement la fin des vérifications, par la disparition du logo... (lorsque l'on est en 627264, les vérifications sont terminées et l'on peut détourner vers notre routine).

On recommence pour la dernière fois et cette fois-ci, ça marche.
Tous les call ont été correctement fixés :)
On fait un dump de la section .text par un /DUMP 401000 251000 text_dump.dat
Il n'y a plus qu'à remplacer alors le code dans le fichier game.exe par le dump.

On peut aussi inclure dans notre Call-fixer, une routine qui renvoie les bonnes valeurs à la routine chargée de vérifier l'intégrité de gamedll.dll.
Dans ce cas, on n'a pas besoin d'utiliser la macro. Par contre, il faut toujours attendre la 4ème vérification d'intégrité sur l'exécutable.

Ce qui donne (despe.asm) :


title call_fixer
.386
.model small, stdcall
option casemap :none
     
.code
    _TextOffset             equ 00401000h
    _TextSize                equ 00251000h
    _RoutineCompare   equ 10017A18h
    _RoutinePatch         equ 10002596h
    _gamedll_check      equ 1000A331h

start:
    pushad
    mov esi,_RoutinePatch
    mov word ptr [esi],0E7FFh
    call @1
@1:
    pop edi
    mov esi, _gamedll_check
    mov byte ptr [esi], 68h
    mov byte ptr [esi+05], 0C3h
    mov dword ptr [esi+01], edi
    add dword ptr [esi+01], offset here3-offset @1
    add edi, offset here1-offset @1
    mov edx, _TextOffset
    mov ecx, _TextSize
   
search_loop:
    cmp word ptr [edx], 15FFh
    jne try_again
    cmp dword ptr [edx+2], 0067A2A0h
    jne try_again
    lea eax, [edx+6]
    pushad
    push eax
    ;jmp dword ptr [67A2A0]
    db 0FFh,25h,0A0h,0A2h,67h,0
here1:
    mov edx, dword ptr [ebp+08h]
    mov dword ptr [edx-4], eax
    mov eax, edi
    mov ebx, _RoutineCompare
    cmp byte ptr [ebx], 01
    jne @2
    mov byte ptr [edx-5], 25h
    inc eax
@2: add eax, offset here2-offset here1
    mov esi, _RoutinePatch+03
    jmp esi
here2:
    pop eax
    popad
try_again:
    inc edx
    dec ecx
    jne search_loop
    popad
    ; jmp eip
    db 0EBh,0FEh

here3:
    push ebx
    lea ebx, dword ptr [ecx+eax]
    cmp ebx, _RoutinePatch+01
    ja @a_
    cmp ebx, _RoutinePatch
    jb @b_
    sub ebx, _RoutinePatch
    add ebx, edi
    add ebx, offset here4-offset here1+1
    mov dl, byte ptr [ebx]
    jmp @c_
@a_:
    cmp ebx, _gamedll_check+05
    ja @b_
    cmp ebx, _gamedll_check
    jb @b_
    sub ebx, _gamedll_check
    add ebx, edi
    add ebx, offset here4-offset here1+3
    mov dl, byte ptr [ebx]
    jmp @c
@b_:
    mov dl, byte ptr [ecx+eax]
@c_:
    pop ebx
    mov eax, dword ptr [ebp-04]
    push _gamedll_check+06
here4:
    ret

    db 3Eh,8Bh
    db 8Ah,14h,08h,8Bh,45h,0FCh
end start


Enfin, il est possible, afin de contourner les checksums sur les routines Laserlock, d'incorporer des routines de Laserlock dans
votre Call-fixer. (voir le tutorial de GáDiX & SkUaTeR [Karpoff Spanish Tutor [1999-2002]).
Et pour contourner ceux sur la section .text de game.exe, on peut créer une table à 3 entrées,
une indiquant l'adresse à patcher dans .text, une autre par ce qu'il faut patcher (adresse correspondante dans
l'IAT) et la dernière, qui indique si l'on a un call API ou un jmp API...
Cette table peut alors être utilisée ultérieurement et indépendamment de l'exécution des routines de gamedll.dll...

C) Restaurer l'Import Table :

On va maintenant supprimer la dépendance de game.exe vis-à-vis de gamedll.dll .
On a deux possibilités, mais d'abord un rappel sur le format de l'Import Table...


Format de l’Image Import Descriptor ou Import Table  (lire « peering inside the PE » de Matt Pietrek !) :

Une entrée (une DLL et l’ensemble de ses fonctions) dans l’Image Import Descriptor est constituée de 5 Dword.

Dword 1 - Characteristics (hint name array)
Ce dword est un pointeur vers le premier élément d’un tableau de pointeurs.
Chaque pointeur de ce tableau pointe vers le hint name suivi du nom d’une fonction.

Dword 2 - TimeDateStamp

Dword 3 - ForwarderChain

Dword 4 - DLL’s name
Ce dword est un pointeur vers le nom de la DLL (null terminated ascii string)

Dword 5 - Import Address Table
Ce dword est un pointeur vers le premier élément d’un tableau d’adresses.
Ce tableau d’adresses fonctionne en parallèle avec celui des pointeurs vers les hint names (noms des fonctions).

tiré de Import Function tutorial.doc / Import-Function in section RDATA.doc de El.CaRaCoL.
Merci à lui ;-)


Première possibilité :

On supprime cette .dll de l'Import Table (celle créée par Laserlock)



Pour cela, on recherche l'occurrence gamedll.dll dans l'exécutable.
On la trouve en 0027A2DE. (library name)
Comme les VO correspondent aux RO (pas de décalage au niveau des sections), on n'a plus qu'à rechercher DEA22700 (adresse "à l'envers"), pour trouver le pointeur correspondant dans l'IT...
Elle est juste en-dessous...
L'IT créée par Laserlock va donc de 0027A2F8 à 0027A3FC (size 0x104).
Lorsque l'on ouvre l'exécutable avec LordPE (PE Editor) et que l'on regarde dans les Directories, on a bien 0027A2F8 (RVA) et 00000118 (Size) pour l'Import Table. On a 00000118 comme Size, car on y inclut 10 Words nuls, indiquant comme il se doit la fin de l'Import Table.
Pour supprimer cette .dll de l'IT, on a juste à modifier le début de cette table, en la faisant commencer en 0027A30C (càd avec KERNEL32.dll).
Dans LordPE, on met alors 0027A30C comme RVA et 00000104 comme Size, pour L'Import Table.

Voici à titre d'illustration l'Import Table générée par Laserlock :


Deuxième possiblité :

Retrouver l'Import Table originale pour qu'elle remplace celle créée par Laserlock. On modifie en conséquence dans les directories. Elle est située en 00278CB8 avec une taille de 00000104. L'IT créée par Laserlock est en fait juste une copie de celle-ci, la seule différence étant l'ajout de gamedll.dll dans la table !!! On fait les modifications dans LordPE.



Ca y est on peut supprimer la .dll de Laserlock, elle ne sert plus à rien maintenant :)
Et le jeu se lance parfaitement...

Rem : il nous reste un "CD-check".
Mais là c'est une autre histoire ;)
En tout cas, il ne nous empêche en rien de cracker Laserlock.

On lance le jeu sans insérer le CD.
On a une jolie boîte, qui s'affiche et demande l'insertion du CD :(



On kille avec le Burn Process de LordPE ou le Kill Task de Procdump, bien plus efficaces que cette merde de Ctrl+Alt+Delete de Windows...
Avant toute chose, il faut s'assurer que tous les fichiers du jeu sont copiés sur le disque dur, et cela même si vous avez fait une installation maximale. (Pas besoin d'y passer une heure non plus :p).
On compare C:\Program Files\Infogrames\Desperados\Game\Data avec F:\GAME\Data et l'on s'aperçoit que le dossier Cinematics a été volontairement "oublié". C'est dommage...
Bon, on va le copier là où il devrait être :)

Ce log d'API Spy confirme...
005B5783:CreateFileA(LPSTR:01DE69A1:"Data\Cinematics\infogrames.bik",DWORD:80000000,DWORD:00000001,LPDATA:00000000,DWORD:00000003,DWORD:00000080,HANDLE:00000000)
005B5789:CreateFileA = FFFFFFFF (GAME.EXE)
005A0601:MessageBoxA(HWND:00000000,LPSTR:01DE68F1:"Veuillez insérer le CD",LPSTR:00000000,DWORD:00000015)

On lance le jeu et plus de problème. Ce n'est donc pas un "CD-check" à proprement parler.
Un vrai CD-check demande le CD alors même que toutes les données sont présentes sur le disque dur...

D) Perspectives et conclusion :

Il existe en fait une grosse faille dans Laserlock...

Tout d'abord, la routine située en 10001DAD, appelée par 1000258D (Gamedll.dll), ne sert pas qu'à calculer les adressses des APIs pour une redirection.
Elle affiche le logo, effectue la vérification du CD, les différents checksums et autres...
Le 1er call Laserlock permet l'affichage du logo.
Le 2ème call Laserlock vérifie l'authenticité du CD inséré.
Si le CD original n'est pas inséré, une boîte de dialogue s'installe et l'exécution de game.exe s'interrompt.
Si le CD original est inséré, le logo disparaît et l'exécution de game.exe se poursuit (et donc celle du jeu).

La faille vient du fait qu'à partir d'un certain temps, gamedll.dll patche elle-même les call Laserlock de game.exe par les call API (comme cela devrait l'être et l'était avant que l'exécutable ne soit protégé...).
Elle fait donc la même chose que notre call-fixer...
Il suffit de mettre un Breakpoint on memory range (bpr) au niveau du code (section .text) pour détecter toute tentative d'écriture.
On retombe en 100024D5 mov dword ptr [ecx], eax.
L'exemple sur lequel j'étais tombé :
ecx = 004563D1 (adresse à patcher)
eax = 006520D4 (adresse dans l'IAT où se trouve l'API concernée)
Le call Laserlock était alors transformé en call GetLocalTime.
Rem : SecuROM v2 a utilisée aussi cette technique (cf. FIFA99).

Si l'on regarde le listing de gamedll.dll désassemblé dans W32Dasm, on voit à cette même adresse, ceci :

Nul doute que c'est crypté !!!
En fait, seule le section .text de gamedll.dll est cryptée et encore seulement en parties (les parties sensibles).
Pour étude, il suffit donc de dumper la section .text et de la coller dans la dll.
On aura alors un listing compréhensible :)
On édite d'abord le PE de cette .dll à l'aide de LordPE.



La section .text va de l'offset 400(RO) à 400(RO)+FC00(RS)=10000(RO) sur le disque.
Elle va de 10000000(Image Base)+1000(VO)=10001000 à 10011000, en mémoire.
Pour dumper, on lance le jeu.
Une fois dans le menu, on réduit.
Toujours dans LordPE, on choisit game.exe dans les processus, click droit, Dump region...
Dans Dump Information, on met 10001000 comme Address et 00010000 comme Size, puis click sur Dump : voilà, on a notre section .text dumpée.
Il ne reste plus qu'à la coller dans la .dll à partir de l'offset 400.

Rem : Il y a un peu de junk code, mais ce n'est pas bien méchant.
La .dll obtenue n'est bien-sûr pas fonctionnelle, puisqu'elle tentera de décrypter du code déjà décrypté, à moins que les nombreux checksums l'en empêchent...

On a ceci à la place : c'est nettement mieux, hein ;)

Rem : cette version décryptée de gamedll.dll (fournie) est riche en enseignement :)

On a déjà beaucoup plus de String References.
On sait maintenant la version de la protection : "v5.00.06  Compile:06/09/2000", qu'elle a été faite par un certain "Petros Skalkos **" (un grec) et on a diverses informations intéressantes comme "\LASERLOK\LASERLOK.IN", "_*NTCDFound*", etc...
Il y a même un copyright pour cette protection anti-copie :o
"*LASERLOK* Copyright (c) 1992-1996 "...

Mais, ce qui est très intéressant, c'est ce que l'on trouve plus haut :




Et plus particulièrement, la comparaison en 1000248A et le saut conditionnel juste après, qui conditionne le patch des call Laserlock.
En fait, un compteur détermine le nombre de fois, que la routine Laserlock a été appellée via les call Laserlock.
Lorsqu'elle a été appelée un certain nombre de fois, ici 0x32 soit 50 (en décimal), Laserlock estime qu'après toutes ses vérifications, elle peut se laisser aller et patcher les call Laserlock, ceci pour un gain de temps et donc de performances.
Et de toute façon, aucun cracker ne peut parvenir jusqu'ici après autant de vérifications ;)
Par contre, les checksums sur la routine Laserlock sont toujours effectués...

Vous voyez certainement où je veux en venir :)
En mettant 0x32 en 100155F0 dès le lancement de game.exe, vous allez sauter l'affichage du logo, la vérification du CD "Laserlocké" (d'ailleurs excessivement longue ; je n'ai jamais vu plus long pour l'instant), les diverses vérifications d'intégrités, etc...
Et Laserlock va nous fixer gentillement tous les call Laserlock à notre place.
Rem : Mettre 0x33 en 100155F0 fait crasher le programme...

On peut donc modifier notre1ère routine comme suit :


title call_fixer
.386
.model small, stdcall
option casemap :none
     
.code

    _TextOffset             equ 00401000h
    _TextSize                equ 00251000h
    _RoutineCompare   equ 10017A18h

start:
    pushad
    call    @1
@1:
    pop edi
    add edi, offset here1-offset @1
    mov edx, _TextOffset
    mov ecx, _TextSize
   
search_loop:
    cmp word ptr [edx], 15FFh
    jne try_again
    cmp dword ptr [edx+2], 0067A2A0h
    jne try_again
    lea eax, [edx+6]
    pushad
    push eax
    ;jmp dword ptr [67A2A0]
    db 0FFh,25h,0A0h,0A2h,67h,0

here1:
    mov eax, edi
    mov ebx, _RoutineCompare
    cmp byte ptr [ebx], 01
    jne @2
    inc eax
@2: add eax, offset here2-offset here1
    mov esi, _RoutinePatch+03
    jmp esi
here2:
    pop eax
    popad
try_again:
    inc edx
    dec ecx
    jne search_loop
    popad
    ; jmp eip
    db 0EBh,0FEh
end start


L'utilisation de la macro reste de mise, à cause des checksums sur la routine Laserlock.
Par contre, il n'y a plus de checksums sur la section .text de game.exe :)

On breake donc à l'OEP de game.exe, on trace jusqu'au 1er call Laserlock (en 006201E4), correspondant au Call GetVersion. Avant de l'exécuter, on met 0x32 en 100155F0.
Après ce step over (F10), on peut détourner vers notre routine, sans oublier la macro :
BPM 10002596 X do "r eip here1;g;"
Rem : On exécute un premier call Laserlock, afin d'initialiser les différentes valeurs...
D'ailleurs ce premier call n'est pas patché en call Getversion...

Une autre faille (mais cette fois-ci, au niveau matériel) ???

Elle concerne la protection physique au niveau du CD.
Non seulement, on peut cracker le jeu sans le CD, mais même sans être crackeur, il serait possible de détourner la protection.
Il suffirait d'ouvrir le fichier caché LASERLOK.IN avec un éditeur hexadécimal, de le sauvegarder (même si l'éditeur n'indique qu'une lecture partielle du fichier).
Ensuite l'on copierait le contenu du CD sauf le LASERLOK.IN, "incopiable" du CD mais l'on intégrerait à sa place, le fichier obtenu précédemment, afin de contourner la protection...
J'ai essayé cette astuce avec ce jeu : elle ne marche pas. Peut-être, ne concerne-t-elle que des versions plus anciennes de la protection ?
Rem : On a tout de même une lecture partielle du fichier LASERLOK.IN avec un éditeur hexadécimal, puisque l'on récupère le contenu du fichier jusqu'à l'adresse 3FD000 pour une taille de 1388000 (le restant étant occupé par des zéros)...
(Le fichier LASERLOK.IN du jeu desperados, obtenu à l'aide d'Alcohol 120% est fourni).

Généralisation :

On va voir qu'il aisé de "concevoir" un unpacker générique, juste en regardant les différences avec un autre jeu, ayant la même protection.
Il s'agit de Worms : World Party (merci à NaG-SCRiM, au passage).
En plus, c'est la même version de la protection ! (Je pensais d'ailleurs que ce jeu avait une version de Laserlock bien supérieure, celle avec le SPEEnc...).
En fait, les "différences" sont plutôt d'ordre visuelles qu'autre chose, lol...

Déjà le dossier Laserlok à la racine du CD contient deux autres fichiers en plus du Laserlok.in :



Le logo change aussi (il est moins original, par contre) :



Enfin, la .dll de la protection porte le doux nom de wwpdll32.dll ;-)

Plus sérieusement, les adresses dans cette .dll sont exactement les mêmes (la .dll d'ailleurs fait la même taille que celle de Desperados)...
Ce n'est pas très malin tout ça...
Localiser les call Laserlock, ici , semble, ici, plus délicat, puisqu'au niveau de l'OEP, ce sont surtout des call MSVCRT (APIs non détournées), que l'on trouve :



Il n'en est rien : pour les localiser, il suffit de regarder après les asciis des différentes fonctions des imports :



Il ne reste plus qu'à chercher la chaîne hexadécimale FF1570C36800, dans la section .text pour en trouver...
Pour trouver le 1er call Laserlock (la 1ère API à être détournée), rien de plus simple : il suffit de placer un bpx quelque part dans l'exécutable, d'attendre le crash suite la mauvaise redirection et de regarder l'adresse poussée dans la pile :-)
Ici, il s'agit de l'adresse 004B52A8 :



Les adresses sont effectivement inchangées (par rapport à la .dll de Desperados) :



Celle du compteur l'est aussi (remarquez la position qu'il occupe par rapport à la chaîne de caractères "Please insert the original CD-ROM in drive") : celle-ci peut ainsi nous servir de "marqueur" pour faire un unpacker générique...



Ce qui est peut-être plus dur, c'est de déterminer l'IT originale, puisque les sections sont décalées...



L'IT créée par Laserlock commence en 1BE3C8 dans le fichier wwp.exe (Raw Offset), mais commence en 28C3C8 (il suffit de regarder les Directories dans LordPE) en mémoire (Virtual Offset) avec une taille de 12C. En mémoire, il faut bien évidemment tenir compte de l'ImageBase en plus...
L'IT originale (détournée) commence en 1BCB00 dans le fichier (Raw Offset), en mémoire elle commencera donc en 28C3C8-1BE3C8+1BCB00, ce qui nous donne 28AB00 (Virtual Offset) à mettre dans les Directories pour une taille de 118...

Voilà, vous avez assez d'informations pour faire votre propre unpacker ;-)
D'ailleurs, [NtSC] d'UNPACKiNG GODS en a fait un (excellent d'ailleurs).
Cette protection n'est finalement pas bien dure...
A bientôt pour la suite de ce tutorial sur Laserlock (la version packée avec le SPEEnc, de 2003 à nos jours, beaucoup plus intéressante...)



Enlever le CD-check de WWP :



C'est hors-sujet mais bon, tant qu'on y est, autant terminer le boulot :)
De toute façon, j'ai horreur d'insérer un CD/DVD pour lancer un jeu...

1ère constatation :
Le répertoire du jeu pèse 36,7 Mo, ce qui est dérisoire en comparaison avec la taille du CD WWP (643 Mo).

2ème constatation :
La routine, qui vérifie la présence du CD n'est pas très dure à trouver.
En debuggant ou même en désassemblant wwp.exe et en cherchant des imports du style GetDriveTypeA / GetVolumeInformationA, on arrive à ceci :





Décortiquons cette routine :
- L'API GetLogicalDrives donne les lecteurs/disques utilisés (que l'on a).
- Parmi ceux-ci, GetDriveTypeA détermine les lecteurs/graveurs CD/DVD.
Cela est fait indirectement, puisque la routine "élimine" d'abord les Remote Drive (Network) par un
cmp dword ptr [ebp-30], 00000004, puis les disques durs, et enfin les "lecteurs en RAM"...
Il ne reste donc plus que les lecteurs/graveurs CD/DVD, après. D'habitude, on a le fameux cmp eax, 00000005...
- Lorsqu'un lecteur CD est détecté, l'API GetVolumeInformationA détermine le volume du média (s'il y en a un) inséré dans ce lecteur CD...
- Enfin la fonction strcmp compare la chaîne de caractères du volume récupéré précédemment à "WWP".

En résumé, un log d'APISpy :

============= Created by APIS32 v. 2.5 =============

0046E2FA:GetLogicalDrives()
0046E300:GetLogicalDrives = 3D (WWP.EXE)
0046E340:GetDriveTypeA(LPSTR:00B9FD20:"C:\")
0046E346:GetDriveTypeA = 3 (WWP.EXE)
0046E340:GetDriveTypeA(LPSTR:00B9FD20:"D:\")
0046E346:GetDriveTypeA = 5 (WWP.EXE)
0046E379:GetVolumeInformationA(LPSTR:00B9FD20:"D:\",LPSTR:00B9FD28:" ",DWORD:00000010,LPDATA:00000000,LPDATA:00B9FD38,LPDATA:00B9FD40,LPSTR:00000000,DWORD:00000000)
0046E37F:GetVolumeInformationA = 0 (WWP.EXE)
0046E340:GetDriveTypeA(LPSTR:00B9FD20:"E:\")
0046E346:GetDriveTypeA = 5 (WWP.EXE)
0046E379:GetVolumeInformationA(LPSTR:00B9FD20:"E:\",LPSTR:00B9FD28:" ",DWORD:00000010,LPDATA:00000000,LPDATA:00B9FD38,LPDATA:00B9FD40,LPSTR:00000000,DWORD:00000000)
0046E37F:GetVolumeInformationA = 0 (WWP.EXE)
0046E340:GetDriveTypeA(LPSTR:00B9FD20:"F:\")
0046E346:GetDriveTypeA = 5 (WWP.EXE)
0046E379:GetVolumeInformationA(LPSTR:00B9FD20:"F:\",LPSTR:00B9FD28:" ",DWORD:00000010,LPDATA:00000000,LPDATA:00B9FD38,LPDATA:00B9FD40,LPSTR:00000000,DWORD:00000000)
0046E37F:GetVolumeInformationA = 0 (WWP.EXE)
...

On pourrait se dire qu'en noppant les sauts conditionnels en 0046E381 et en 0046E3A7, c'en est fini pour ce CD-check (comme pour la plupart des CD-checks)... Et ben non...
Vous aurez droit à des erreurs de chargement et puis à un joli crash...
Pourquoi ?
Parce que si vous insérez le CD du jeu dans un de vos lecteurs CD, la routine récupère la lettre de ce dernier (le mov byte ptr [ebp-08], al en 0046E3AF, puis mov al, byte ptr [ebp-08] en 0046E3C2) pour charger divers fichiers...
Et si vous forcez les sauts, le jeu va vouloir charger certains fichiers à partir de votre 1er lecteur CD...

Quelques exemples :

Ma configuration en lecteurs : un lecteur de disquette A:\, un disque dur C:\, un lecteur DVD D:\ et un graveur DVD E:\.
En forçant les sauts, le jeu veut charger certains fichiers à partir de D:\.
Pour conserver la structure du CD et faire au plus simple, j'ai d'abord copié le dossier Data du CD à la racine de mon disque dur (C:\). Il suffit alors de corriger la valeur de al pour qu'elle coïncide avec le disque dur où vous avez copié le dossier Data. J'ai donc transformé l'instruction add eax, 00000041 en add eax, 00000040 à l'adresse 0046E3AC (tout en laissant les 2 sauts précédents noppés) et il n'y a plus aucun problème :)
Je sais que c'est bourrin mais cela a l'avantage d'être rapide et efficace.
Et puis quel intérêt d'être subtil avec des choses qui ne le sont pas ? ;)
Bien-sûr, ce n'est pas la seule possibilité pour cracker ce CD-check...

Annexes :
____________________________________________________________________________________________
GetLogicalDrives(A)
____________________________________________________________________________________________

Retourne un bitmask représentant les différents lecteurs disponibles.

DWORD GetLogicalDrives(VOID);

Code de retour : Si la fonction réussit, la valeur retournée est un bitmask représentant les lecteurs disponibles.
Bit position 0 est le lecteur A, bit position 1 est le lecteur B, bit position 2 est le lecteur C, et ainsi de suite.
Si la fonction échoue, la valeur retournée est zéro...


____________________________________________________________________________________________
GetDriveType(A)
____________________________________________________________________________________________

Determine la nature d'un lecteur/disque.

UINT GetDriveType(LPCTSTR lpRootPathName);

lpRootPathName // adresse racine (chemin d'accès)

Pointe vers une chaîne de carctères (terminée par zéro), qui spécifie le répertoire racine du disque pour obtenir des informations dessus. Si lpRootPathName est nul, la fonction utilise la racine du répertoire actuel.

Code de retour : La valeur de retour spécifie le type de "lecteur/disque". Elle peut avoir les valeurs suivantes :

Value Meaning
0 The drive type cannot be determined.
1 The root directory does not exist.
2 DRIVE_REMOVABLE The drive can be removed from the drive.
3 DRIVE_FIXED The disk cannot be removed from the drive.
4 DRIVE_REMOTE The drive is a remote (network) drive.
5 DRIVE_CDROM The drive is a CD-ROM drive.
6 DRIVE_RAMDISK The drive is a RAM disk.

E) Remerciements :

Greetz :

ACiD BuRN, ArthaXerXès, Black Check, cdkiller (ProtectionID), CyberBob Jr, ^DAEMON^, Dark-Angel, DecOde12, diablo2oo2, Duelist, El-Caracol, EliCZ, Elraizer, evlncrn8, Fravia+, +Frog's Print, KeopS, Gádix, GRim@, G-RoM, Iczelion, kilby, Laxity, Lorian, LutiN NoIR, MackT, MrOcean, NeuRaL NoiSE, Neutral AtomX, Ni2, Nody, [NtSC], +ORC, Pedro, Peex, PEiD (snaker, Qwerton, Jibz), Psyché, Pulsar, +Pumqara, Ricardo Narvaja, Skuater, +Spath, +splaj, Stone, TeeJi, The Owl, tHeRaiN, TaMaMBoLo, +Tsehp, Tola, woodmann, +Xoanon, [yAtes], yoda ... et tous ceux que j'ai oublié et qui contribuent activement à la scene par leurs tutoriaux, tools et autres...

all the icedump team, ARTeam, CracksLatinos, DREAD, FRET, ShmeitCorp, UCF2000, UNPACKiNG GODS, TMG, all game groups...

All +HCU Students
All ppl of RCE Messageboard

these great sites :
http://207.218.156.34/krobar/index.html
http://arteam.accessroot.com/
http://tuts4you.com/
http://www.woodmann.net/forum/index.php
http://www.woodmann.net/yates/index.htm
http://www.exetools.com/forum/

Special Greetz :

Christal : Merci pour tout ce que tu as fait pour la scene française et ton aussi grande implication. Mes plus grands respects :-)
R!SC : Thanks, Master, for your tuts about commercial CD-Rom protections, like Safedisc, SecuROM, VOB ProtectCD, etc... :-)
All the team FFF ;-)

Dynasty pour son aide à la traduction :-)

Final words :

J'espère que vous aurez eu plaisir à lire ce tutorial.

Si l'étude de cette protection vous intéresse, je peux toujours vous uploader les cibles du tutorial. Il suffit pour cela de m'envoyer un mail à :
dWx5c3NlMjAwOV9mckB5YWhvby5mcg==
(base64 encoded)

Desperados : Wanted Dead Or Alive est mon jeu culte :-)
C'est un excellent jeu tactique dans la lignée des Commandos, mais bien meilleur que ceux-ci par son ambiance western superbement rendue, son humour et l'enchaînement judicieux des missions au travers d'une "vraie" histoire. Quant à la dimension tactique, elle vous donnera du fil à retordre. A découvrir exclusivement (et ça change un peu des FPS)...
Tiens "Wanted Dead Or Alive", cela me fait penser à quelqu'un : il se reconnaîtra ;-)
Pour ceux qui n'aiment pas les jeux, il reste en tout cas la bière Desperados comme valeur sûre :p
Les enfoirés, c'est à cause d'eux, que Desperados 2 a dû changer de nom en Western Commandos : La revanche de Cooper...
J'espère d'ailleurs que la suite de cet excellent jeu ne me decevra pas...
"If you like a game, Buy it !"


uLysse_31, le 14 / 01 / 2007

"There is a crack, a crack in everything... That's how the light gets in."


Copyright © uLysse_31