Protection commerciale : CD-Cops v1.31
Editeur : Link Data Security (http://www.linkdata.com/)
Outils nécessaires :
OllyDbg v1.10
ImpRec 1.6f
LordPE
Masm v9
Un éditeur hexadécimal
OS: Win XP
Il n'est pas nécessaire d'avoir le CD d'authentification pour appliquer ce tutorial.
Ce tutorial est détaillé de façon suivante :
A) Trouver l'OEP et dumper à ce niveau
B) Restaurer les stolen bytes à l'OEP et les imports
C) Fixer les nanomites
D) Conclusion et remerciements
Additional Info
PPeter - The Hungarian Guru of CD's :
The CD-Cops protection system is not using electronic "fingerprint" code. The check routine only verifies the physical angle between the first and last accessible logical block on the CD. This verification is very good to identify an original disc. The 8-digit code holds the angle information and the check routine compares this with the CD.
CD-Cops is a very good protection system, because the software developers don't need special mastering machine, they only have to add their verifying code to the main application. The angle between the first and last logical block is different for every disc written by CD-R writer, due to the Laser Calibration Area, different CD-R types, etc.
mcLallo :
Commercial ready-made copy protections are always interesting. Here is another one, used to protect CD-ROMs. It's a nice one, timers making sure the program will crash if something takes more time than it should (as if someone would put a breakpoint somewhere and fool around), a couple of checksums to make sure no one's been changing anything in the code, and - of course - some self-modifying code decrypting itself just when it needs to be used. It even claims to be able to separate perfect CD copies from the original CD!
B) Restaurer les stolen bytes à l'OEP et les imports :
On lance EuV12.exe et on dumpe "à la volée" EuV12.W_X (on l'appellera dump.exe).
On sait maintenant que l'OEP est en 005A3400.
On ouvre donc dump.exe sous Ollydbg et l'on se rend à l'OEP :
En analysant le dump de la pile :
On peut déduire ceci :
- La première valeur poussée sur la pile correspond à une adresse pointant sur la pile.
Les 2 premières instructions sont donc PUSH EBP / MOV EBP,ESP
- Les 3ème et 4ème valeurs poussées sur la pile (006D7758 et 005D490E) permettent de déduire un PUSH 006D7758 en 005A3405 et un PUSH 005D490E en 005A340A.
- Il ne reste plus qu'à trouver la 3ème instruction, de taille 2 bytes. La deuxième valeur poussée sur la pile étant 0, l'instruction PUSH 0 convient donc.
Au final, on obtient ceci :
Les deux valeurs dans le dump de la pile (en vert) sont identiques et correspondent bien aux deux PUSH EAX en 005A341D et en 005A341E.
Il ne reste plus qu'à sauver ces modifications dans dumped.exe .
Toujours dans dump.exe (le dump à la volée), on cherche et sélectionne un call [API] ou jmp [API].
Un click droit Follow in Dump > Memory address permet d'atterrir dans l'IAT.
Elle commence en 010C36D0 et se termine en 010C3CC0 (Size = 5F0) :
...
En ouvrant dump.exe à l'aide de LordPE et en utilisant son Structure Lister, on s'aperçoit qu'il manque dans l'Import Table, les imports KERNEL32.dll (voir aussi imports.txt) :
Il faut donc reconstruire une Import Table avec ImpRec :
On sélectionne dumped.exe et on obtient un dumped_.exe reconstruit :).
On renomme ce dernier et paf, ça plante :(.
C) Fixer les nanomites :
On lance Ollydbg, on désactive tous les types d'exceptions dans Debugging options, pour que l'on s'arrête sur l'exception en question.
On lance l'exécutable et l'on atterrit ici :
Ohhh comme c'est mignon !!! Des nanomites :p.
Et il y en a un paquet...
On fait un Search for > All commands > int3 pour obtenir la liste (cf. int3_table.txt).
On n'oublie de relever de le code d'erreur de l'exception :
On remarquera aussi des sortes de données juste après les int 03. Le code est bien trop propre et structuré pour être honnête :p.
Une autre chose apparaît claire sur plusieurs nanomites : ce sont des instructions sur 6 bytes, qui ont été détruites...
On doit donc retrouver le processus, qui debuggue EuV12.W_X et fait en sorte qu'il exécute indirectement l'instruction détruite...
On lance Universalis (EuV12.exe) que l'on réduit, on lance Ollydbg, on attache le processus EuV12.QZ_ .
Par intuition, je cherche dans le code la constante 80000003 (code d'erreur) :
On obtient :
On met un bp en 00411E9C sur le CMP EAX, 80000003. On clique sur l'application pour l'agrandir...
Bingo ! On breake :).
On voit d'ailleurs bien une liste de comparaison de codes d'erreurs à ce niveau :).
Maintenant on traçe le code jusqu'en 00411F02. On rentre dans ce call et l'on surveille les registres.
En traçant un tout petit peu, on voit apparaître l'adresse d'une nanomite (0055C15F) en ecx. Comme par hasard !
On voit aussi que les données situées après l'int 03 sont utilisées. Le hasard fait bien les choses :p.
En 00411C86, on a en SS:[EBP-8] :
Dans le dumped d'EuV12.W_X, on a bien :
Que fait cette routine ? Par un petit algorithme, elle se sert des données (le dword) situées juste après l'int 03.
Elle renvoie un numéro dans AX (ici, 0F1). A quoi sert-il ?
En continuant de tracer, on arrive à ceci :
Tiens, tiens, une référence à une API !!!
En 00411CFA, EDX + EAX*4 peut se traduire ainsi : adresse d'origine + numéro de rang * 4.
On a donc à faire à une table (une IAT), qui commence en 9AAD88 et se termine en 9AB354 (taille = 5CCh, soit 173h entrées).
On dumpe donc la table obtenue (imports.dat).
Le numéro de rang est celui, obtenu par l'algorithme précédent.
Ainsi, à l'adresse 9AAD88 + 0F1 * 4 se trouve l'adresse de l'API PostMessageA.
Maintenant, on sait que les nanomites permettent l'émulation d'appel aux APIs.
Mais, est-il question de call [API] ou de jmp [API] ?
En ouvrant avec Ollydbg, un dumped d'EuV12.W_X et en faisant un Search for > All intermodular calls, on s'aperçoit qu'il n'y a pas beaucoup d'imports Kernel & User présents. De plus, seuls les appels des imports de la librairie MSL_All-DLL90_x86.dll se font par des jmp [API] :
On peut donc opter pour l'hypothèse selon laquelle seuls des call [API] sont émulés, hypothèse, qui sera confirmée ultérieurement.
Un autre point important est la présence d'une table de nanomites.
On s'en aperçoit, en traçant le call 00411B28, situé en 00411CC6 :
Elle commence à l'adresse pointée en [00418574], càd en 009AB378 et se termine en 9AB8F0.
On dumpe donc la table obtenue (nanotable.dat).
Au départ, j'avais restauré les instructions en me servant de cette table.
Malheureusement, il reste de très nombreuses nanomites...
A quoi sert-elle donc, cette table ?
En analysant sa structure, on s'aperçoit qu'il y a une valeur (un byte), qui sépare chaque adresse de nanomite (en fait, ce sont les adresses+1 des int 03, qui sont stockées) :
Cette table stocke pour chaque nanomite correspondant, le nombre de fois, dont il a été effectué / appelé / debuggué...
Quelle en est la raison ?
Lorsque ce nombre atteint 010h, CD-Cops patche directement le code au niveau de la nanomite correspondante, et ce pour un gain de performance...
Comme le debugger EuV12.QZ_ doit patcher au niveau du thread principal de EuV12.W_X, il doit faire appel à l'API VirtualProtectEx (il faut tracer à l'intérieur du call 00411D89) :
Et pour patcher, il utilise la fonction non documentée NtWriteVirtualMemory
du noyau (SSDT) :
A la première utilisation, CD-Cops utilise le couple classique LoadLibraryA / GetProcAddress pour obtenir l'adresse de celle-ci, puis l'émule :
L'appel à cette fonction se fait en 0040A888 :
Cette fonction du noyau est en fait appelée par l'API WriteProcessMemory. Il suffit de jetter un oeil à l'adresse de cette dernière :
Le prototype de la fonction NtWriteVirtualProcess est d'ailleurs très proche de celui de l'API WriteProcessMemory :
NtWriteVirtualMemory (
ProcessHandle
BaseAdress
Buffer
NumberofBytesToWrite
NumberofBytesWritten ) ;
Pour savoir, à quel endroit s'effectue le patch, il suffit de regarder les paramètres dans la pile, avant l'appel à cette fonction :
EuV12.QZ_ patche donc EuV12.W_X, au niveau du nanomite incriminé. Ce patch se fait en 0055C15F sur 6 bytes (nombre de bytes, correspondant à un call [API] ).
L'opcode E8 correspond à un call, notre hypothèse était donc valide !
Pour équilibrer le code, un nop est ajouté.
Beurk !!!
Un appel direct à l'API PostMessageA sera donc effectué par la suite...
L'adresses des APIs étant OS-dépendant, ce comportement est donc à éviter ! (Il suffit de dumper à l'OEP comme précédemment pour ne pas avoir ce problème).
Qu'est-ce que la SSDT ?
System Service Dispatch Table
SSDT ou System Service Dispatch Table est un tableau de descripteur de service situé dans le Noyau de windows, utilisé pour diriger des appels système vers un traitement approprié : table d'adressage des API.
Point d'accroche dans la table SSDT
"SSDT hooking" Le "crochetage" de la table SSDT en vue de sa modification est une des techniques fréquemment utilisées par les rootkits. En modifiant cette table, ils peuvent réorienter l'exécution vers leur code au lieu du module de traitement (fonction) originellement appelé. Certaines de ces fonctions sont crochetées tant * par les rootkits malsains que les anti-rootkits
Sur Windows XP, cette table est en lecture seule, mais cette protection peut être contournée.
Exemples d'appel système détourné :
* Pour les thread, processus, (et jobs) : NtAssignProcessToJobObject, NtCreateThread et NtTerminateThread, NtOpenProcess et NtTerminateProcess...etc
* pour les fichiers : NtQueryDirectoryFile, NtDeleteFile...etc
* pour les pilotes : NtLoadDriver et NtUnloadDriver
* Pour la mémoire : NtProtectVirtualMemory et NtWriteVirtualMemory
* Divers : NtCreateKey, NtReplaceKey
Par voie de conséquence, cette table du noyau est l'une des plus contrôlées par les utilitaires de détection des rootkits. Sa restauration en cas de modification non désirée est une protection supplémentaire que certains anti-rootkits proposent.
http://fr.wikipedia.org/wiki/System_Service_Dispatch_TableSur la pile, on trouve lpContext (un pointeur vers une structure CONTEXT, qui contient le context à mettre dans le thread spécifié) :
Voici le nouveau context (modifié par CD-Cops) :
Ainsi, EuV12.W_X reprendra la main directement au niveau de l'API et au premier ret, retournera au bon endroit dans le code de EuV12.W_X, grâce à la modification de la pile. Il s'agit donc d'une émulation des appels aux APIs.
- EuV12.QZ_ rend la main au processus cible, grâce à l'API ContinueDebugEvent.
La boucle est bouclée :).
Passons à la restauration de ces nanomites, grâce à une routine.
Pour restaurer les nanomites, j'ai utilisé la LDE (Length Disassembler Engine) de BeatriX, pour effectuer un début de désassemblage.
Une recherche par pattern est en effet très inefficace, puisque l'on retombera dans de nombreux cas, en plein milieu d'instructions...
Ici, la LDE renvoie la longueur de l'instruction et pour tomber sur une nanomite, il suffit que pour une certaine adresse, la LDE renvoie une longueur de 1 et que l'opcode correspondant soit 0CCh.
Comme, il n'y a pas de junk code ou autres bizarreries dans le code de EuV12.W_X, il suffit d'implémenter la recherche de nanomites, en sautant linéairement d'instruction en instruction, en ajoutant la longueur renvoyée par la LDE...
Voici le descriptif de cette LDE :
Voici quelques explications sur ma routine (nanomite.asm, commenté et assemblable sous Masm v9) :
La routine va d'abord copier l'IAT d'EuV12.W_X à la suite de notre routine, en enlevant les séparateurs entre les différentes librairies. Ainsi, elle devient identique à l'IAT de EuV12.QZ_, utilisée pour la correspondance avec les nanomites.
Ensuite, on va parcourir le code à la recherche d'une nanomite (int 03).
Lorsque la longueur renvoyée par la LDE est égale à 1 et que l'opcode est bien 0CCh, on a bien une nanomite.
Si ce n'est pas le cas, on augmente l'adresse à "désassembler" de la taille de l'instruction précédente...
Dans le cas où la LDE renvoie le code -1 (erreur), on incrémente et on finira bien par retomber dans une situation normale...
Lorsque l'on tombe sur une nanomite, on applique l'algorithme permettant de retrouver l'API correspondante (via son numéro), ainsi que son flag.
L'algorithme a tout simplement été rippé...
Si le flag est égal à 1, on a à faire à un call [API].
Si le flag est égal à 0, on a à faire à un call @1 / @1: jmp [API]. Il faut donc dans ce cas, rechercher le jmp [API] correspondant...
On patche en conséquence et on continue jusqu'à la fin du code...
Il ne reste plus qu'à dumper la section .text et à la remettre dans le dump précédent.
NB : La routine assemblée est à copier dans la section .data (writeable) sous Ollydbg (Binary >
Binary paste).
On lance alors le dump restauré et par miracle, l'application se lance sans problème :p.
Bye-bye CD-Cops ;).
D) Conclusion et remerciements :
La restauration au niveau des nanomites aurait pu être perturbée par la présence d'int 03 (de bourrage), servant à délimiter des blocs de code (à séparer certaines routines de sub-routines). Je suppose que ceci dépend du compilateur utilisé. Ici, l'application a été développée en cpp (Il suffit de regarder dans les String Data References de EuLaunch.exe, qui, lui, n'est pas protégé, les nombreuses références à des composants *.cpp) et les blocs de code sont uniquement séparés par des nops ;).
Si ce n'était pas le cas, il aurait fallu étudier avec la LDE, ce qui suivait les int 03... Présence d'autres int 03 à la suite ou de bytes aléatoires (instructions invalides) puis d'instructions valides, 6 bytes après l'int 03 ?
Plus de détails sur les autres mécanismes de protection (encryption / checksum / GetTickCount-trick) sont disponibles dans les tutoriaux de McLallo (CD-Cops,
Another ready-made protection annihilated) et de LaptoniC (CD-Cops v1.76). Ils sont disponibles dans le dossier docs.
L'étude de CD-Cops constitue une bonne initiation aux nanomites et permettrait d'attaquer des nanomites plus complexes, comme celles d'Armadillo (émulation de sauts). La méthode employée devrait rester valable sur Armadillo. L'utilisation d'un debugger (émulant le comportement de celui d'Armadillo) ou des veh (vectored exception handler)
, c'est crade :p.
Le seul souci pourrait apparaître avec les int 03 de bourrage, insérés par certains compilateurs. Mais, même dans ce cas, il est possible de ne pas se laisser avoir avec la table d'Armadillo, qui contient de fausses nanomites. Il suffit de désassembler linéairement à l'aide de la LDE à la suite du saut jusqu'à l'endroit où l'on se retrouve en exécutant ce saut. Si la différence est nulle, c'est bien une nanomite. Si ce n'est pas le cas, on saute (aléatoirement) en plein milieu d'une instruction et ce n'est pas une nanomite. Bref, ce n'est qu'une hypothèse :p.
Un tutorial, qui parle de nanomites d'Armadillo : http://www.reteam.org/papers/e72.pdf
Même si la version 13 d'Universalis (édition 2008) est protégée avec CD-Cops v1.82, la protection demeure exactement identique.
Reste à savoir si Universalis va changer sa protection suite à la sortie des cracks...
Ils étaient passés des dongles à Safecast, puis à CD-Cops. Que vont-ils faire ?
Leur produit édition 2009 est maintenant terminé (la compilation des launchers se fait en Juin / Juillet) et la protection de leur produit (les fichiers créés par CD-Cops) se fait vers Août / Septembre. C'est donc en train de se jouer ces mois-ci pour l'édition 2009 ;). Suspense...
La seule chose, qui m'échappe, c'est qu'ils utilisent de vieilles versions de CD-Cops (1.31 & 1.82), alors que sur le site de Link Data Security, on voit qu'une version 3 était déjà sortie en Septembre 2003...
L'éditeur d'Encyclopaedia Universalis aurait-il fait une réserve de protections, qu'ils écouleraient bien plus tard ?
EDIT : J'ai été grandement déçu par la protection d'Universalis (l'édition 2009). CD-Cops a été remplacé par une protection minable (faite maison) en java !!!
Il suffit de modifier un minimum, Application.class dans universalis-dvd-app3.jar, pour coutourner cette protection.
Non seulement, la protection est lame, mais, en plus, il est désormais possible de faire une copie du CD d'authentification, puisque la "protection" se contente de vérifier la présence de la chaîne "22024012618190721396961752119927502051841399111229" dans le fichier 00000001.TMP du CD d'authentification, dont le volume est KEY CD. Bien évidemment, ce n'était pas le cas avec CD-Cops...
Reste plus qu'à attendre l'édition 2010, en espérant que la protection soit beaucoup plus intéressante. Et pourquoi pas un CD-Cops v3 ?
Au final, même si CD-Cops n'est en quelque sorte qu'un Armadillo allégé, ce n'est pas forcémment un mauvais choix. En effet, contrairement à Safecast, les CDs d'authentification produits par CD-Cops restent très difficiles à cloner / émuler avec les outils actuels (CloneCD / Alcohol 120 % / BlindRead / Daemon Tools)...
Bien évidemment, les unpackers / decrypters publiques présents sur le net ne marchent pas...
On peut les trouver sur http://www.gamecopyworld.com/cd-cops.htm.
Pour la petite anecdote, en lisant le readme de CD-Cops Emulator v1.0, je suis tombé sur ça :
Ca m'a bien fait rire. Analyst, qui se vante d'être dans beaucoup de greetz, a oublié de nous citer celui-là ;-).
Remerciements :
BeatriX pour sa LDE
mars
Christal et tous ses compères, R!SC, +Frog's Print & +Spath, FRET et bien évidemment, tous les membres de FFF ;)
Voilà c'est fini...
"There is a crack, a crack in everything... That's how the light gets in."
uLysse_31, le 07-08-2008
Copyright © uLysse_31