ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Crack de C-Dilla SafeDisc 2.70
Tutorial sur le jeu Mafia
____________________________________
Crack et tutorial écrit par Peex
(24 septembre 2002)
Résumé: Ce document présente une méthode de cracking complète et détaillée de SafeDisc 2.70, sans pré-requis sur les versions
antérieures de cette protection. La solution choisie pour ce tutorial est d’écrire un patch ‘SafeDiscRemover’ afin de
restaurer le programme original tel qu’il était avant sa protection. Pour la voie suivie dans ce tutorial, la principale
difficulté du crack tient dans quatre protections anti-dumping que nous aborderons en détail.
o Fichier cible: Game.exe (Exécutable du jeux Mafia)
- Version: 1.0
- Taille: 3 237 078 octets
- CRC32: 894A6B6C
o Logiciels utilisés:
- SoftIce v4.05 (NuMega)
- IceDump v6.026
- FrogsIce v1.10b (Frog's Print & Spath)
- Masm32 v7.0
- LordPE Deluxe v1.4 (Yoda)
- ADump v1.0 (tHeRaiN / UCF)
- Import REConstructor v1.3 (MackT / UCF)
- HexWorkshop v2.54 (BreakPoint Software)
- CD original du jeu Mafia (Illusion Softworks)
o Difficulté: Assez facile (mais long à réaliser)
Table des matières du tutorial
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
1. Introduction
2. Généralités
3. Recherche de l’OEP : Une technique parmi d’autres
4. Résolution du problème des références systèmes de .rdata rerouté, 1er essai
5. Résolution du problème des références systèmes de .rdata rerouté, 2nd essai
6. Solution fonctionnelle pour le problème des références systèmes de .rdata rerouté (Protection 1)
7. Etude et solution pour les Call externes rerouté en JMP (Protection 2)
8. Etude et solution pour les Call internes rerouté (Protection 3)
9. Etude et solution pour le code modifié à la volée (Protection 4)
10. Fix du header et reconstruction de la table des importations
11. Patch du CD-Check spécifique au jeu
12. Conclusion et perspectives
13. Avertissement, notes de l’auteur
14. Remerciements
15. Annexes : Code source du patch (Part A & B)
1. Introduction
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Ce tutorial présente une méthode permettant de retirer toutes les protections d'un programme protégé par SafeDisc. Si cette
technique n'est pas spécialement difficile à comprendre et à réaliser, elle est en revanche relativement longue. Un minimum de
connaissances est tout de même nécessaire, notamment pour l'écriture de code assembleur, pour les techniques de dumping ainsi que
pour le fix de ces dumps. Toutefois, ce tutorial se veut "self-contain", aussi je détaillerais toutes les opérations jusqu'à la plus
simple pour le rendre accessible au plus grand nombre de personnes.
2. Généralités
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Commen?ons par quelques généralités sur SafeDisc. Il faut savoir que cette protection est une surcouche appliquée à un programme
finalisé. C'est à dire que les concepteurs du jeu n'ont placé aucune protection dans leur code. Ils font appel à C-Dilla pour
protéger leur produit fini. Le générateur de protection va alors encrypter et protéger le programme original, puis placer un loader
à la fin du fichier.
Dans les premières versions de SafeDisc, le programme principal était encrypté et stocké dans un fichier .ICD et le loader placé
dans un exécutable séparé qui se présentait comme le fichier à démarrer pour lancer l'application. Aujourd'hui tout tient dans un
seul et même exécutable, ce qui ne change rien au problème et à sa difficulté.
Si l'on regarde de plus près le loader, on remarques plusieurs choses: Il contient des routines anti-SoftIce qui sont elle même
cryptés, ce qui signifie que l'on ne pourra pas modifier ces routines anti-SoftIce sur le disque en vue de les neutraliser
définitivement. En effet, on ne trouvera pas le code de ces routines dans le fichier puisque ce code est crypté. Une solution
consiste à coder une fonction qui modifie le code en mémoire après que la fonction de décryptage aie fini son travail, mais les
choses se compliquent singulièrement lorsque l'on a du code multi-crypté (pour le cas étudié, ceci est cependant faisable).
Pour passer les protections anti-débuggeur, la solution la plus simple consiste cacher notre débuggeur (SoftIce) à l'aide de
programmes bien sympathiques comme par exemple FrogsIce (de Frog's Print). Ce programme rend inefficace les routines de détections.
Les techniques de détection de SafeDisc étant quasiment resté identiques entre les versions, vous pouvez toujours utiliser le preset
"C-Dilla SafeDisc" dans FrogsIce (facultatif, car le mode par défaut marche bien aussi). Pour information, les routines de détections
utilisées sont les suivantes:
- API Test_Debug_Installed / IsDebuggerPresent
- MeltIce (avec \\.\SICE et \\.\SIWVID)
- INT 68h
FrogsIce passe très bien toutes ces protections. Un gros merci au passage à Frog's Print pour son magnifique travail. (A noter que
vous pouvez aussi utiliser IceDump comme anti-détecteur.)
Encore une remarque générale, après lancement, le loader va décrypter ces routines de protection et les charger en mémoire comme
un processus à part. Par ailleurs, ces routines restent utilisées tout au long de l'exécution du programme principal (le jeu). On
peut avec ces quelques constatations entrevoir un processus anti-dumping. Le code original a été modifié pour faire appel aux
routines de protections, de sorte qu’elles doivent être en mémoire pour que le programme fonctionne correctement. Dans la pratique,
le générateur de protection de C-Dilla remplace un appel à un sous routine du programme (call <ref>) par un appel à une routine de
sécurité SafeDisc. Une fois que cette routine de sécurité a terminé son travail, elle saute sur le code de la routine originale, et
le programme continue. La seule condition à respecter est de ne pas modifier les registres du système. Si l'on dumpe simplement le
programme principal, on ne disposera donc pas des routines de sécurité qui permettent au programme de fonctionner, et le programme
crashera dès qu'il trouvera une référence à une des fonctions de sécurité. Le but étant d'enlever la vérification du CD, on peut
suivre deux voies:
- Le "loader/patcheur": Cette méthode a recours à un loader qui charge en mémoire le programme principal DEJA décrypté et qui charge
en plus les routines de sécurité patchées. En effet, une fois que l'on se trouve à l'OEP (Original Entry Point), le loader de
SafeDisc a déjà effectué le CD-Check et le décryptage du programme, il ne reste plus que les anti-dumping tricks. Les routines de
protections chargées en mémoire ne sont là que pour assurer le bon fonctionnement des anti-dumping tricks.
- Le "remover": Cette méthode propose de restaurer le programme comme il était avant d'être modifié par le générateur de protection
de C-Dilla. Il faut alors recoder les instructions qui ont été modifiés pour enlever les processus anti-dumping. Le point de départ
de cette méthode est de disposer du programme à son OEP afin de procéder à sa restauration. Pour reconstruire le programme, on
s’aidera des routines de sécurité de SafeDisc qui font ce travail, mais on les modifiera légèrement afin de les mettre à notre
avantage.
(Pour être complet, il faudrait aussi parler des méthodes qui émulent la protection CD.)
Dans ce tutorial, j'ai choisi la seconde méthode, à savoir le "remover" qui est bien plus propre que la première méthode une fois
que l'on a terminé le travail. Lorsque les protections deviendront vraiment difficiles à restaurer, la première méthode deviendra
peut-être intéressante. Pour cette version de SafeDisc, le reverse engineering est relativement facile comme nous allons le voir.
La principale difficulté du crack de SafeDisc réside dans quatre techniques anti-dumping. Ce tutorial propose donc une solution
pour retirer ces protections et ainsi de permettre à l'application de se lancer sans le loader qui lui est associé et qui gère la
protection. En plus de contr?ler la protection par CD et de décrypter le programme principal, le loader reconstruit des bouts de
code du programme original au fur et a mesure de son exécution. Il redirige également les appels systèmes sous diverses formes ainsi
que certains appels internes. Nous allons donc aborder successivement ces quatre protections anti-dumping de SafeDisc qui font sa
difficulté.
Il faut commencer par disposer du programme à son OEP, mais avant de commencer à chercher l'OEP, je vais vous donner les quelques
informations utiles du header du jeu Mafia (Récupérées avec LordPE).
ImageBase (IB) : 00400000
ImageSize (IS) : 0030225C
EntryPoint (EP) : 002FF05E
Section V_Offset V_Size R_Offset R_Size Flags
.text 00001000 002399BC 00001000 0023A000 60000020
.rdata 0023B000 0000AF5F 0023B000 0000B000 40000040
.data 00246000 000B4B7C 00246000 0000F000 C0000040
.rsrc 002FB000 00000D76 00255000 00001000 40000040
stxt774 002FC000 0000204F 00257000 00003000 E0000020
stxt371 002FF000 0000325C 0025A000 00004000 E0000020
3. Recherche de l’OEP : Une technique parmi d’autres
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
[Note : Je vais être insultant pour certains d'entre vous, mais j'ai dis que je détaillais tous, alors je le fais.]
Si vous regardez le point d'entrée du programme à l'aide de ProcDump ou de LordPE, vous obtiendrez le point d'entrée du loader de
SafeDisc et non le point d'entrée du jeu. Lorsque le loader à finit son travail, à savoir vérifier le CD et décrypter les différentes
sections du programme principal, il saute alors à l'OEP et le jeu se lance. C'est au moment où on est à l'OEP que l'on va pouvoir
travailler. Avant ce point, le programme n'est pas décrypté, et après des variables sont initialisés, l'image est modifiée, et donc
inutilisable dans la plupart des cas.
Pour une protection similaire à SafeDisc (avec CD-Check et cryptage) mais qui ne disposerait pas de protections anti-dumping, un
dump à l'OEP donnerait un exécutable fonctionnel une fois fixé. Ca c'est la puissance du dump, et les gens de C-Dilla l'on bien
compris, aussi, leurs recherches ne se situent plus uniquement dans l'amélioration du cryptage (car le dump court-circuite le
cryptage) mais aussi dans les techniques anti-dumping (on peut aussi rajouter la difficulté de copie de CD, mais ceci ne nous
intéresse pas ici).
Pour déterminer l'OEP, il existe plusieurs techniques, et vous en trouverez pas mal en lisant des tutoriaux. La plus laborieuse
(déconseillée) est de tracer le loader en regardant quand il quitte son segment pour arriver dans la section .text du programme (la
référence de la première instruction moins l'IB (ImageBase) = l'OEP). On peut aussi monitorer les jmp, j*, call, call[ref], jmp[ref]
et ret, ce qui est aussi très long. En restant dans les techniques plut?t moyennes, on peut également utiliser un désassembleur à
partir d'un dump du jeu en cours d'exécution (donc non valide pour la reconstruction du programme original) pour essayer de repérer
certaines structures de début de programme bien connues, mais cette technique est hasardeuse et échoue souvent si le programme
contient des bouts de codes avec des astuces anti-désassemblage qui perturbent le désassembleur.
Je vais à présent vous détailler la technique que j'ai utilisée pour ce programme, mais ce n'est pas la seule solution, aussi,
choisissez la méthode que vous voudrez tant que vous trouvez l'OEP.
Une fois SoftIce masqué, je lance le jeux et je switch immédiatement sous SoftIce pour poser un breakpoint "GetVersion" (bpx
GetVersion). GetVersion est une fonction de kernel32.dll qui est souvent appelé tout au début des grosses applications. Donc lorsque
SoftIce s'arrête à mon breakpoint, il y a de fortes chances pour que je ne sois pas très loin du début du programme que je risque
fort de reconna?tre. A noter aussi que le loader de SafeDisc peut aussi disposer de cet appel système lors de son initialisation,
donc il faut vérifier en sortant de l'appel système (GetVersion) que l'on se trouve bien dans la section .text du programme
(section contenant le code du jeu). Généralement l'OEP se trouve vers le fin de a section .text (constatation/règle empirique).
00627338 5D POP EBP
00627339 C3 RET
(B) 0062733A 55 PUSH EBP
0062733B 8BEC MOV EBP,ESP
0062733D 6AFF PUSH FF
0062733F 68B8EF6300 PUSH 0063EFB8
00627344 688C956200 PUSH 0062958C
00627349 64A100000000 MOV EAX,FS:[00000000]
0062734F 50 PUSH EAX
00627350 64892500000000 MOV FS:[00000000],ESP
00627357 83EC58 SUB ESP,58
0062735A 53 PUSH EBX
0062735B 56 PUSH ESI
0062735C 57 PUSH EDI
0062735D 8965E8 MOV [EBP-18],ESP
(C) 00627360 FF151CB16300 CALL [0063B11C]
(A) 00627366 33D2 XOR EDX,EDX
Voici ci-dessus une capture d'écran partielle de SoftIce effectué après être revenu de l'appel kernel32 GetVersion. Le point de
retour se situe en (A). La fonction appelant GetVersion est donc en (C), et on reconna?t bien typiquement un début de programme en
(B). On est bien dans la section .text, vers la fin, et de plus un RET juste au dessus vient nous confirmer le commencement d’une
nouvelle routine. On voyant tout ceci, on peut dire que c'est gagné pour la détermination de l'OEP.
Remarque: Une fois l'OEP déterminé, vous pouvez chercher à l'aide d'un traceur l'instruction qui pointe sur l'OEP. En utilisant le
traceur de IceDump (/tracex 62733A), on découvre que l'instruction précédente se trouvait à 6FF119, ce qui une fois désassemblé nous
dévoile un joli 'jmp 62733A'. Remarquer également que cette instruction n'était pas cryptée et qu'elle était donc visible simplement
depuis l'EP. Une fois que l'on conna?t la technique de SafeDisc, il est très facile de retrouver manuellement ce jump. Donc rien de
dur, ni pour nous, ni pour un SafeDiscRemover automatique.
4. Résolution du problème des références systèmes de .rdata rerouté, 1er essai
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Voila, l'OEP est déterminé et notre travail serait quasiment terminé si il n'y avait pas les protections supplémentaires anti-
dumping. Justement, venons-y, remarquez que l'appel système GetVersion est noté:
CALL [0063B11C] (en (C) soit 00627360)
alors que traditionnellement, il est noté comme ceci sous SoftIce:
CALL [KERNEL32!GetVersion]
Ce n'est pas une erreur de SoftIce et cela mérite qu'on y regarde de plus près. Cet appel (Call) pointe sur la section .rdata qui
contient en temps normal (et entre autre) les références des appels externes (appel aux dll externes au programme). Mais si l'on
regarde cette référence (à 63B11C qui se trouve dans .rdata), au lieu de trouver une référence un appel système (ici à kernel32), on
trouve cette référence: 02BF1DE0
Voici un petit extrait de la section .rdata pour vous faire une idée:
0063B11C:>02BF1DE0< 02BF22A3 02BF2766 02BF2C29
0063B12C: 02BF30EC 02BF35AF 02BF3A72 02BF3F35
0063B13C: 02BF43F8 02BF48BB 02BF4D7E 02BF5241
0063B14C: BFF95DD3 02BF5BC7 02BF608A 02BF654D
Et si l'on regarde le code pointé par ces références, on trouve toujours sensiblement la même chose, mais ce ne sont pas les
routines système attendues. Je vous donne le code pour la référence pointé en 63B11C soit : 02BF1DE0.
(D) 02BF1DE0 688710EABF PUSH BFEA1087
02BF1DE5 9C PUSHFD
02BF1DE6 60 PUSHAD
02BF1DE7 54 PUSH ESP
02BF1DE8 68201EBF02 PUSH 02BF1E20
(E) 02BF1DED E857B54FFF CALL 020ED349
02BF1DF2 83C408 ADD ESP,08
02BF1DF5 6A00 PUSH 00
02BF1DF7 58 POP EAX
02BF1DF8 61 POPAD
02BF1DF9 9D POPFD
02BF1DFA C3 RET
On peut déjà penser comme je l'ai évoqué plus haut à un mécanisme anti-dumping, ou les références des appels systèmes stockés dans
.rdata sont remplacé par des pointeurs sur des routines de sécurité de SafeDisc. Une fois ces routines SafeDisc lancées, elles
appellent à leur tour le vrai appel système demandé à l'origine.
A noter également que cette partie de la mémoire n'appartient pas à un processus lancé par le loader de Windows, c'est donc
probablement une routine de sécurité qui l'a alloué pour ses besoins et qui a décrypté (ou calculé) du code à l'intérieur. En
revanche, l'appel que l'on trouve à l'intérieur de cette routine va nous renseigner. Regardons à qui appartient le code qui se situe
en 20ED349. Pour cela, on lance un petit map32 sur la référence:
map32 20ed349
Owner Obj Name Obj# Address Size Type
~DF394B .txt2 0001 0187:020A1000 0000F1E9 CODE RO
~DF394B .text 0002 0187:020B1000 0003AC35 CODE RO
~DF394B .txt 0003 0187:020EC000 00001E02 CODE RO
~DF394B .txt4 0004 0187:020EE000 0000011B CODE RO
~DF394B .txt5 0005 0187:020EF000 0000017F CODE RO
~DF394B .rdata 0006 018F:020F0000 00009E17 IDATA RW
~DF394B .data 0007 018F:020FA000 00018345 IDATA RW
~DF394B .reloc 0008 018F:02113000 00005382 IDATA RO
~DF394B stxt774 0009 018F:02119000 000009BC UDATA RO
Il s'agit de la principale "DLL-like" de sécurité (de SafeDisc) qui est lancé par le loader de SafeDisc. Une remarque au passage,
~DF394B est stocké sur le disque dur sous forme de fichier temporaire (dans un répertoire temporaire) et effacé après terminaison du
programme. Ce n'est d'ailleurs pas la seule et vous pouvez vous amusez à dumper ces routines pour les reverser si ?a vous amuse.
Revenons à notre problème et commen?ons par tracer notre appel "CALL [0063B11C]" en (C). On s'aper?oit qu'en sortant de l'appel
"02BF1DED: CALL 020ED349" en (E) on arrive directement dans la fonction système kernel32. Donc si je reprends, le Call du programme
principal en (C) va appeler une routine de sécurité de SafeDisc, et cette routine de sécurité va lancer l'appel système désiré. Cet
appel a donc été -rerouté- ou encore -relogé- (j'utiliserai indifféremment les deux termes). Tous les appels système (à kernel32.dll,
user32.dll et advapi32.dll) ont ainsi été modifiés par le générateur de protection de SafeDisc. Il devient logique que si l'on dumpe
le programme principal, on ne disposera pas de la routine de sécurité qui se trouve dans un processus à part, et donc le programme
crashera dès le premier appel système (absence de code, ou zone invalide). C'est donc une protection anti-dumping.
Comme nous avons choisi d'écrire un remover plut?t qu'un loader, il va donc falloir remplacer les appels aux routines de SafeDisc
par les valeurs originales des appels systèmes.
En fait, c'est plus compliqué que cela... Ce que je viens d'expliquer était valable pour les anciennes protections de SafeDisc
(les personnes qui se sont déjà attaqué aux premières versions de SafeDisc ont du reconna?tre). Entre temps, le système à évolué et
c'est un poil plus complexe, mais plut?t que de vous donner directement la solution, j'ai choisi de vous expliquer le cheminement
vers la solution. Certes le document sera plus long, et les premier patches du "remover" ne seront pas fonctionnels, mais le but de
ce tutorial est de vous expliquer la réflexion à mener pour casser ce genre de protection, et non pas de vous fournir une solution
toute cuite pré-machée sans explications (pour ?a, il suffit de chercher un crack sur le net).
Je le répète encore une fois : les premières analyses et patchs ne fonctionneront pas ! Je les détaille ici dans un but
pédagogique. (Pour les personnes qui connaissent déjà les versions légèrement antérieures de SafeDisc, vous allez écarquiller les
yeux... :o) De même, SafeDisc est composé de plusieurs protections anti-dumping, et lors de ma progression dans ce document, je
ferai comme si je ne connaissais rien des autres protections restant encore à déplomber. Bref, je détaillerai l'évolution du crack de
SafeDisc. Ceci étant dit, continuons.
Ma première idée pour résoudre ce problème à été d'appeler chaque référence relogée de .rdata (ou encore : chaque appel système
relogé par SafeDisc) en modifiant auparavant la fonction de SafeDisc qui calcule les références des appels systèmes afin qu'elle me
retourne dans un registre la référence de l'appel système original au lieu de l’appeler. Une fois que l’on dispose de la valeur
originale d’un appel relogé, on le remplace dans .rdata et le tour est joué.
Analysons maintenant la routine qui calcule la valeur originale de l’appel système pour voir comment faire cela :
(Appel de cette routine 02BF1DED CALL 020ED349 en (E))
020ED349 55 PUSH EBP
020ED34A 8BEC MOV EBP,ESP
020ED34C 83EC40 SUB ESP,40
020ED34F 53 PUSH EBX
020ED350 56 PUSH ESI
020ED351 57 PUSH EDI
020ED352 F0FF05440C1002 LOCK INC DWORD PTR [02100C44]
020ED359 740E JZ 020ED369
.
.
.
020ED499 50 PUSH EAX
020ED49A E82381FDFF CALL 020C55C2
020ED49F 59 POP ECX
020ED4A0 F0FF0D440C1002 LOCK DEC DWORD PTR [02100C44]
020ED4A7 780C JS 020ED4B5
020ED4A9 FF35F4DB1002 PUSH DWORD PTR [0210DBF4]
020ED4AF FF1548000F02 CALL [KERNEL32!SetEvent]
(F2)020ED4B5 8B650C MOV ESP,[EBP+0C]
020ED4B8 61 POPAD
020ED4B9 9D POPFD
(G2)020ED4BA C3 RET
.
.
.
020ED6E2 50 PUSH EAX
020ED6E3 E8DA7EFDFF CALL 020C55C2
020ED6E8 59 POP ECX
020ED6E9 F0FF0D440C1002 LOCK DEC DWORD PTR [02100C44]
020ED6F0 780C JS 020ED6FE
020ED6F2 FF35F4DB1002 PUSH DWORD PTR [0210DBF4]
020ED6F8 FF1548000F02 CALL [KERNEL32!SetEvent]
(F) 020ED6FE 8B650C MOV ESP,[EBP+0C]
020ED701 61 POPAD
020ED702 9D POPFD
(G) 020ED703 C3 RET
Quelques explications sur le fonctionnement de cette routine de sécurité que l’on va utiliser : Lorsque l'on arrive en (G), la pile
contient l'adresse le l'appel système original (dans notre cas, kernel32!GetVersion) et l'adresse de retour de la fonction appelante
: 00627360 CALL [0063B11C] en (C). Vous l’avez compris, on va tout simplement récupérer cette référence système, et revenir à notre
patch. (Note : la routine arrive en (G2) si elle à déjà effectué le calcul auparavant. Donc pour être complet, il faudrait modifier
ces deux endroits, mais nous nous occuperons de ceci par la suite.)
Avant de continuer, j'explique le fonctionnement d'un Call pour certains d'entre vous. Lorsque l'on fait un Call, le système change
l'eip pour la mettre sur l'adresse de destination, mais il met aussi dans la pile l'adresse de retour du call, soit une instruction
après le Call (ce qui fait eip+5 si c'est un Call <ref> et eip+6 si c'est un Call[ref]). La routine appelée se termine par un RET,
ce qui retire de la pile la valeur de retour du call et change l'eip à cette valeur. On est alors revenu une instruction après le
Call. On pourrait traduire un Call <ref> par:
push eip+5
jmp <ref>
Et un RET par:
pop eip
Voila pour cette parenthèse. Donc quand le système procède au premier RET, il saute sur l'appel système (kernel32!GetVersion), et
quant la routine GetVersion se termine et arrive sur son RET final, on retourne au programme principal, juste après le Call[0063B11C].
(D'une simplicité enfantine, et je ne risque pas de descendre plus bas dans le détail des explications... :o)
Donc pour récupérer la valeur de l'appel système original, il suffit de faire un POP edx en (G), et la référence système qui
aurait du être appelée passe dans le registre edx. La seconde référence dans la pile servira à revenir à l'appelant, notre patch. On
pourrait donc insérer notre POP à la place du RET en (G) et rajouter un RET après, mais on modifierait du même coup le code qui se
trouve en dessous de (G) (d’un byte car un pop prend un byte). L'astuce consiste donc à placer un JMP en (F) pour amener le programme
dans ADump où l’on dispose d’autant de place que l’on veut. Une fois que l'on est dans notre segment de mémoire, on restore les
instructions que le jump à effacé et on rajoute un POP edx avant le RET (Le jump consomme 5 bytes, c'est pour cela qu'on le met plus
en arrière).
Voyons donc à quoi ressemble la fin de la fonction de calcul (020ED349) après modification (afin qu'elle retourne la valeur de l'
appel système original dans edx.)
020ED6F0 780C JS 020ED6FE
020ED6F2 FF35F4DB1002 PUSH DWORD PTR [0210DBF4]
020ED6F8 FF1548000F02 CALL [KERNEL32!SetEvent]
020ED6FE E931493081 JMP 833F2034
Et à l'adresse pointé par le JMP, on restore les instructions effacées par le jump et on rajoute notre patch:
833C2034 8B650C MOV ESP,[EBP+0C]
833C2037 61 POPAD
833C2038 9D POPFD
833C2039 5A POP EDX <<< Notre superbe patch
833C203A C3 RET
On va ensuite taper un peu de code sous SoftIce pour réaliser le programme de fix que l’on a imaginé. Pour ma part, j’avais tout d’
abord commencé par écrire mon petit programmes à l'OEP, mais ceci n'était pas très propre et j'ai alors utilisé ADump pour créer une
zone mémoire libre afin de stocker mon code. Un avantage non négligeable est que si le jeu crashe, le code est toujours stocké dans
ADump. (A noter que l'on peut aussi utiliser IceDump pour créer un segment mémoire libre.)
Voici donc le programme qui parcourt .rdata à la recherche d'appel système relogé par la routine de protection de SafeDisc. Les
appels relogés sont recomposés en appelant la routine spécifique de SafeDisc, puis stocké dans ADump, un peu après le code. Il faudra
ensuite dumper ces références pour les recoller dans sur la section .rdata de l’exécutable dumper à l’OEP.
833C2000 B800B06300 MOV EAX,0063B000
833C2005 BB00606400 MOV EBX,00646000
833C200A 81380000BE02 CMP DWORD PTR [EAX],02BE0000
833C2010 720E JB 833C2020
833C2012 81380000C002 CMP DWORD PTR [EAX],02C00000
833C2018 7306 JAE 833C2020
833C201A FF10 CALL [EAX]
833C201C 8913 MOV [EBX],EDX
833C201E EB04 JMP 833C2024
833C2020 8B10 MOV EDX,[EAX]
833C2022 8913 MOV [EBX],EDX
833C2024 0504000000 ADD EAX,00000004
833C2029 83C304 ADD EBX,04
833C202C 3D50B26300 CMP EAX,0063B250
833C2031 75D7 JNZ 833C200A
833C2033 CC INT 3
Cette capture d’écran de SoftIce correspond au code plus lisible (jump et commentaires) ci dessous:
;___________________________________________________________________________________________________________________________________
; Début du code du RemoveSafeDiscPatch <Essai 1>
;ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
mov eax, 63B000h ; Début de la section .rdata où l'on va scanner les références.
mov ebx, 833C3000h ; Début de la zone où l'on va stocker les références originales (Zone ADump + 1000h).
_loop:
cmp dword ptr[eax], 02BC0000h ; Vérifie si c'est un appel relogé (zone 2BC0000 à 2C00000).
jb _copy ; Si ce n'est pas le cas, on ira juste copier la référence.
cmp dword ptr[eax], 02C00000h ;
jae _copy ;
call dword ptr[eax] ; Appelle la fonction de sécurité de SafeDisc patchée.
; edx contient alors la référence à l'appel système original.
mov dword ptr[ebx], edx ; On copie cette référence dans .data+n.
jmp _next ; Et on continue le traitement.
_copy:
mov edx, dword ptr[eax] ; Ce n'est pas une référence relogée, on la copie simplement.
mov dword ptr[ebx], edx ;
_next:
add eax, 4 ; On avance d'un dword dans .rdata.
add ebx, 4 ; De même pour le pointeur de ebx
cmp eax, 63B250h ; Continue l'analyse jusqu'a .rdata+250h.
jnz _loop ;
int 3 ; Break ici quant le patch a terminé.
;___________________________________________________________________________________________________________________________________
; Fin du code du RemoveSafeDiscPatch <Essai 1>
;ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Explication sur le fonctionnement du programme: (Rappel: Ce patch n'est pas fonctionnel !)
Ce programme balaye .rdata à .rdata+250h (.rdata+250h = fin des appels relogés; repéré sous SoftIce) en testant pour chaque
référence si elle est comprise entre 2BE0000 et 2C00000 (Zone des routines de SafeDisc qui gèrent cette protection). Si cette
référence est relogée, alors on va appeler cette référence pour récupérer la référence originale de l'appel système alors stocké
dans edx. Il faut bien s?r auparavant patcher la routine 020ED349 pour qu'elle retourne la valeur de la référence originale dans edx.
Voila, le patch est écrit et paré à fonctionner, on lance le jeu Mafia en posant un breakpoint à l'OEP (62733A) puis une fois que
SoftICe break, on change l'EIP pour la mettre au début de notre programme stocké dans ADump. On pose alors un breakpoint sur l'
interruption 3 (bpint 3) et on lance le programme. On peut également tracer quelques cycles pour voir si tout se passe comme prévu.
Le patch tourne, analyse les 0x250h valeurs qu'on lui avait demandé et SoftIce break sur interruption 3. Bon, Jetons un petit coup d'
oeil en mémoire pour voir les modifications avant de tester le jeu.
Valeurs des références aux appels système originaux stocké dans ADump:
833C3000: BFE815EA BFE8167D BFE81534 BFE81644
833C3010: BFE819AA 00000000 00981040 00000000
833C3020: 7CD73DF7 00000000 009C4860 00000000
833C3030: BFFA19F5 BFF77B30 BFF764C0 BFF77B8F
833C3040: BFF77B57 BFF76FD1 BFF7713B BFF77C1C
833C3050: BFF7C8BD BFFA0AAB BFF779AE BFFABDEA
.
.
.
833C3100: BFFC63C0 BFF77D63 BFF77D81 BFF77D9F
833C3110: BFF76F3B BFF76DA8 BFFA0ACB >BFF92F1B< (En 64611C, soit 63B11C pour .rdata, on a la référence de l’appel système
833C3120: BFF8C5DA BFF777AD BFF77716 BFF77A90 kernel32!GetVersion appelé en (C))
833C3130: BFF77741 BFF779D5 BFF96347 BFF76A38
...
Avant de continuer les tests, il faut que ces données soient dans .rdata. Comme toujours, on a plusieurs solutions : on peut faire
une copie de mémoire à l’aide de SoftIce, ou procéder à un dump des références et les recharger ensuite dans .rdata. Le second cas
présente l’avantage de conserver les références que l’on a calculé si les choses se passent mal tandis que la première méthode est
plus rapide. Je vous donne les deux :
- Copie de mémoire :
m 833C3000 L 250 63B000
- Dump avec IceDump :
/dump 833C3000 250 c:\rdataFix.bin
/load 63B000 250 c:\rdataFix.bin
On a donc à présent nos modifications dans .rdata. A voir ce résultat, on peut penser avoir restauré les appels systèmes, car ?a y
ressemble bien. D’ailleurs si on jette un coup d'?il au premier appel système après l'OEP, on retrouve bien la référence originale à
l'appel système GetVersion écrite en clair dans SoftICe :
00627360 FF151CB16300 CALL [KERNEL32!GetVersion]
A la place de:
00627360 FF151CB16300 CALL [0063B11C]
Mais malheureusement, c'est l'un des rares cas valide... En effet, si on lance à présent le programme on assiste à un beau crash.
Ce n'est donc pas si simple que cela, ce qui marchait par le passé ne marche plus, et cette protection a bien évolué.
5. Résolution du problème des références systèmes de .rdata rerouté, 2nd essai
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Pour comprendre le problème, il faut trouver un cas non valide et le débugger. Trouver un cas non valide n'est pas très dur,
SoftIce s'en chargera pour vous dès que le programme crashera. On peut alors comparer l’appel système qui cause le crash dans deux
cas : (le crash est des fois un peu après l’appel système lui-même)
- Le cas fonctionnel, où l’on n’a effectué aucune modification des références des appels système de .rdata.
- Le cas où l’on a fixé .rdata avec notre patch.
Et la, on se rend tout simplement compte que le programme n’appelle pas la bonne routine système...
Par exemple, l’appel système en 0062CC35 (du programme fonctionnel non patché)
0062CC35 FF15ECB06300 CALL [0063B0EC]
appelle la référence BFF77716 qui correspond à l’appel système GetModuleHandleA.
Mais notre programme (mal) patché appelle la référence BFFA1392 qui correspond à l’appel système GetEnvironmentVariableA :
0062CC35 FF15ECB06300 CALL [KERNEL32!GetEnvironmentVariableA]
Le programme va donc crasher à l’appel de cette fonction ou un peu après.
Visiblement, notre patch de correction des références de .rdata se trompe quelque par. Si l’on trace l’appel depuis le programme
original (non patché), celui-ci renvoie la bonne référence, alors que lorsque l’appel est fait depuis notre patch, la référence est
erronée.
Si l’on y réfléchit, logiquement, ceci ne peut être du qu’au contexte dans lequel se trouve le système lorsque l’appel à la
fonction est effectué. Or le contexte se compose essentiellement de deux chose : les registres systèmes, et la pile. En procédant
par élimination, on se rend compte que les registres n’ont aucune influence sur la détermination de l’appel système original. Par
contre, la pile va influencer le calcul de la référence de l’appel système. En fait, la routine de calcul de SafeDisc se sert de la
référence posée dans la pile par l’instruction Call pour déterminer la référence système à appeler (Elle se sert aussi d’une autre
valeur posée dans la pile par les routines référencées par .rdata: exemple en (D)). On va donc faire une vérification manuelle pour
vérifier notre hypothèse.
Modifions par exemple le premier ‘Call [ref .rdata]’ trouvé, à savoir le Call [GetVersion] situé en (C). Arrivé à son eip, on
assemble le code pour un autre appel système, par exemple notre CALL [0063B0EC] (a 627360 : call [63B0EC]) puis en trace ce call,
une fois entré dans la fonction, on va tout de suite modifier la première valeur de la pile (mise par le call). On va ainsi faire
croire à cette routine qu’elle a été appelée d’un autre point, ou plus exactement du call en 62CC35. On va donc éditer ce dword a l’
aide de SoftIce et le modifier pour le mettre en 62CC35+5 soit 62CC3A (sous SoftICe, e esp, puis passage de bytes en dword, ou
écrivez en BigEndian)
Et effectivement, le call modifié renvoie bien à l’appel système valide attendu : GetModuleHandleA. Il va donc falloir modifier
notre programme pour qu’il fasse croire à la routine de sécurité de SafeDisc que l’appel a été lancé à l’endroit du call. Ceci va
entra?ner une modification essentielle dans notre programme, il va falloir balayer l’intégralité de la section .text (le code du
programme) pour trouver les call reroutés afin de trouver des cas valides pour modifier .rdata. Aussi, notre patch ne va plus faire
des Call d’appels systèmes reroutés, mais des ‘push <FakeRef> ; jmp <ref .rdata>’.
C’est ce que se propose de faire le programme qui va suivre. Mais nous ne sommes pas au bout de nos surprises, et cette version
du patch ne fonctionne pas non plus. D’ailleurs elle ne traite qu’une protection sur les quatre (et la traite mal).
Comme les patchs commencent à devenir longs à écrire, que l’écriture de code sous SoftIce est pénible et que j’ai autre chose à
faire que calculer manuellement les références de mes jumps sous SoftIce, nous allons utiliser un compilateur assembleur pour coder
nos programmes (masm32 par exemple). Autant tout de suite utiliser cette méthode car les protections deviennent de plus en plus
complexes, et les patchs aussi. On y gagne donc en temps et en patience.
Voici la procédure à suivre : On compile notre patch écrit en assembleur pour obtenir un fichier exécutable, puis on charge cet
exécutable dans ADump. Une fois arrivé à l’OEP du jeu, on change l’EIP pour le mettre au début du code de notre patch. Le patch étant
relativement simple, le header prend 0x200h octets et le code du patch commence donc à +0x200h par rapport à l’endroit ou on l’a
chargé dans ADump. (Ce patch root’s ne sera donc jamais exécuté tel quel, et sera donc toujours chargé en mémoire manuellement pour
être utilisé sous SoftIce.) Comme auparavant, il faudra toujours modifier la routine de SafeDisc de calcul des références.
Maintenant, le code de cette deuxième tentative de fixage des .rdata :
;___________________________________________________________________________________________________________________________________
; Début du code du RemoveSafeDiscPatch <Essai 2>
;ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
title RemoveSafeDiscPatch <Essai2>
.386
.model small, stdcall
option casemap :none
.code
; Ce patch compilé est à charger dans ADump (ou tout segment mémoire libre).
ADump equ 833DE000h ; Adresse du début du segment mémoire créé par ADump
; (à modifier en fonction de son initialisation).
; Constante dépendante du programme cible a fixer (Valeurs pour le jeu Mafia: Game.exe).
_IB equ 00400000h ; (ImageBase du programme cible)
_text equ 00001000h ; (section faisant partie du programme cible)
_textSize equ 002399BCh ; "
_rdata equ 0023B000h ; "
_rdataSize equ 000B4B7Ch ; "
start:
; ==> Procédure principale de scan de la section .text. On cherche les call de type "Call [ref]" <==
pushfd ; On sauvegarde les registres.
pushad ;
mov edx, (_IB+_text) ; Début de la section .text
_mainLoop:
cmp word ptr[edx],15FFh ; Es-ce un "Call [ref]" (FF 15)?
je _check1 ; Si oui, on pousse l'analyse à _check1.
_returnCheck: ; Point de retour des routines ultérieures.
inc edx ; Incrémente l’index edx pour l’analyse suivante.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _mainLoop ; Si non, alors on continue.
popad ; On restore les registres.
popfd
int 3 ; Fin de l'analyse, retour à SoftIce pour dumping.
; ==> Première procédure de vérification : Test de validité de zone pour la référence <==
_check1:
cmp dword ptr[edx+2], (_IB+_rdata) ; Le call pointe sur .rdata à .rdata+250 ? (Zone des routines relogées)
jb _returnCheck ; Non, alors ce n'est pas un appel système relogé par SafeDisc
cmp dword ptr[edx+2], (_IB+_rdata+250h) ;
jae _returnCheck ;
; On a ici un call [.rdata à .rdata+250]
; Reste à vérifier si cet appel est relogé par SafeDisc.
; ==> Seconde procédure de vérification : Test de validité de zone pointé <==
_check2:
mov eax, dword ptr[edx+2] ; Met la valeur du pointeur de .rdata+n dans eax
; Si l’appel est compris entre 02BE0000 et 02D00000 c'est un appel à la
; routine de protection de SafeDisc: ~DF394B
; Les cas déjà traités (appel système +BC000000) sont ignorés du même coup.
cmp dword ptr[eax],02BE0000h ;
jb _returnCheck ; Si ce n'est pas un appel à la routine de protection de SafeDisc, on
cmp dword ptr[eax],02D00000h ; ne touche à rien, car cet adresse est "saine"
jae _returnCheck ;
; Arrivé ici, on restore l'appel système original.
; ==> Procédure de restauration des valeurs des appels systèmes <==
_sysCallFix:
; Sauvegarde des registres avant appel inutile, la fonction de SafeDisc est propre.
; On fait croire à la routine de protection qu'elle a été appelée par une fonction
; du jeu (Fake). (pour info: CALL [ref] = push (eip+6), jmp [ref] )
add edx, 6 ; Adresse de retour du call pour le call a fixer.
push edx ; La routine de protection de SafeDisc utilise l'adresse de retour du
; call appelant (dans la pile) pour déterminer l'appel système original.
sub edx, 6 ; On remet edx comme avant car on s'en sert comme pointeur dans la boucle principale du scan.
jmp dword ptr[eax] ; Appel d'une routine SafeDisc qui appelle la routine générale de conversion en (E).
; ATTENTION ! Avant de lancer ce patch, modifiez la routine de conversion de SafeDisc.
; pour qu'elle retourne l'adresse de l'appel système original dans ECX.
_continue: ; Point de retour pour la fin de la routine de conversion modifié.
pop ebx ; On trash la valeur de retour qui a servi à la fonction de conversion (call fake).
mov dword ptr[eax], ecx ; Corrige .rdata+n avec la valeur de ecx (appel système original).
jmp _returnCheck ; Ok, on a traité un cas, on continue.
_jumpHere: ; La modification (JMP) de la routine générale de conversion doit pointer ici.
nop ; Repère pour la modification manuelle dans SoftIce de la routine générale de conversion.
mov esp,[ebp+0Ch] ; On replace le code que l'on a enlevé | (8B 65 0C)
popad ; | (61)
popfd ; | (9D)
pop ecx ; Originellement: RET (C3). La pile contient alors l'adresse de l'appel système,
; on remplace ce ret par un pop ecx pour récupérer cette valeur.
jmp _continue ; On revient à notre patch.
end start
;___________________________________________________________________________________________________________________________________
; Fin du code RemoveSafeDiscPatch <Essai 2>
;ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Avant de poursuivre sur l’exécution de ce patch, une petite note sur les droits d’accès aux différentes sections du programme. Si
vous regardez les attributs des section .text et .rdata, vous constaterez qu’elles sont en lecture seulement. Or notre patch a besoin
d’écrire dans .rdata pour faire sauter la première protection (réécriture des références des appels systèmes originaux). Vous avez
comme toujours plusieurs solutions et il n’y a pas qu’une fa?on de procéder comme le dit toujours Larry Wall. Vous pouvez soit
modifier à chaud les droits d’accès sur les plages de mémoire, soit modifier les attributs de ces deux sections dans le header du
programme à l’aide de LordPE par exemple. Cette seconde technique n’est valable que si aucun checksum n’est effectué sur le programme
et c’est le cas ici. Donc ne vous en privez pas car cette méthode est de plus persistante (pas de manip. à refaire à chaque fois que
vous lancez le programme).
Lan?ons le patch puis commen?ons par regarder ce que les conversions ont données. Voici un petit extrait de la table des appels
systèmes externes dans .rdata :
0063B000: BFE81644 BFE81644 BFE81534 BFE81644
0063B010: BFE8167D 00000000 00981040 00000000
0063B020: 7CD73DF7 00000000 009C4860 00000000
0063B030:>02BE04EF> BFFA1B27 BFF764C0 >02BE1338<
0063B040: BFF77B57 BFF777AD >02BE2181<>02BE2644<
0063B050: BFF77B57 >02BE2FCA< BFF771D4 BFF77B8F
0063B060: BFF8516B BFF76DA8 BFFA13D7 BFFC6389
0063B070: BFFA1C7C BFF8516B BFF841CB >02BE5F68<
0063B080: BFF8E150 BFF75770 BFF9C947 BFFA1B27
0063B090: BFF76FD1 BFFA1B4A BFF77CC6 BFF77917
0063B0A0:>02BE8A43< BFF7E06D BFF77D9F BFF76A38
0063B0B0: BFF77D3D BFF7CE18 BFFC6055 BFF9DA56
0063B0C0: BFF75881 >02BEB51E< BFF9C63C >02BEBEA4<
0063B0D0: BFF8ABBC BFFA1B27 BFF9600D BFF9DB34
0063B0E0: BFF88BF2 BFF76EA3 BFF9160B BFF77716
0063B0F0: BFFA19F5 BFF75770 BFFA19F5 BFF7755A
La constatation que l’on peut faire, c’est qu’il reste quelques références (02BE0000 - 02D00000) non traitées (indicateur ><). On
peut se demander si tout simplement ces appels ne sont pas utilisés par le programme. C’est néanmoins peu probable, car si le
compilateur les a mis là, c’est qu’il avait une raison de le faire (ok ok, le compilateur de microsoft ne réagit pas toujours
logiquement, passons...). Reste que ceci n’est pas très normal et pas propre. Passons à la phase de test, remettez en place le code
que vous avez modifié dans les routines de sécurité par mesure de sécurité, dès fois qu’une autre protection l’utilise (et c’est le
cas).
Vous vous en doutez, le programme crash lamentablement. Je suis un ane... Et bien pas tout à fait, car je ne pouvais pas me douter
d’une chose avant de tomber dessus, et d’avoir au préalable réalisé tout ceci. Maintenant, c’est ce petit détail que nous allons voir.
6. Solution fonctionnelle pour le problème des références systèmes de .rdata reroutées (Protection 1)
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Tout d’abord, lorsque l’on analyse quelques cas sur l’ensemble des appels relogés de .rdata, on se rend compte qu’une partie des
adresses recalculées est tout de même valide. Une analyse plus poussée (reverse-engineering) de la routine générale de calcul des
références systèmes originales n’est pas nécessaire, et il suffit d’avoir un peut d’imagination pour comprendre ce qui se passe.
Pris indépendamment, nos calculs sont corrects, mais ces calculs de références ne sont pas valables pour touts les appels du
programme. Dans le jeu non patché, deux appels différents à la même référence de .rdata donnent des références d’appels systèmes
différents.
Relisez ces deux dernières phrases, ?a doit faire -tilt-. C’est ce que l’on appelle un phénomène d’insight (phénomène opposé à
une compréhension logique et progressive). Passons...
On ne peut donc pas une fois pour toute changer les valeurs de .rdata en les faisant pointer sur les appels système originaux. La
routine de sécurité de SafeDisc gère deux paramètres, et la méthode que l’on vient d’utiliser n’en prend en compte qu’un en fixant
comme constant le second (la table .rdata). Ce n’est donc pas possible comme cela.
Il va donc falloir utiliser encore un peu notre tête. La solution que j’ai trouvé (il y en a s?rement d’autres) consiste à
construire une table .rdata temporaire contenant les références appels systèmes restaurés comme pour l’essai 1 (donc fausse pour une
partie des appels). Ensuite, pour chaque appel du programme à une référence relogée par SafeDisc, on regarde la fonction système
originale pointée par cet appel et on recalcule le ‘Call [ref]’ en fonction de notre nouvelle table. Finalement, on copie notre
nouvelle table sur l’ancienne. Je ne suis pas rentré dans les détails pour vous donner l’idée générale, nous allons maintenant voir
cette technique de plus près.
- Etape 1 : On commence par construire une nouvelle table .rdata où chaque appel système relogé est remplacé par la référence de
l’appel système original. Bien s?r, comme nous venons de la dire, cette table ne sera pas valide pour tous les appels du programme,
mais ?a ne fait rien. Cette table est construite a partir d’un petit patch comme celui que l’on a vu dans l’essai 1. (On ne l’a donc
pas écrit pour rien ;) C'est-à-dire que l’on balaye .rdata à .rdata+250h (fin des appels relogé) en appelant chaque appel relogé. La
fonction de calcul de SafeDisc est comme auparavant modifiée pour revenir dans notre patch avec la valeur de l’appel système
original dans un registre.
- Etape 2 : La table construite dans l’étape 0 est invalide pour la plupart des appels (call [.rdata]) du programme. Mais nous allons
ici corriger tout les Call du programme pour les rendre valide. C’est simple, lorsque l’on trouve un Call [.rdata], on regarde si ce
dernier est relogé, et si c’est le cas, on récupère la référence de l’appel système original comme à notre habitude. On cherche alors
cette référence récupérée dans notre nouvelle table de références. Une fois celle-ci trouvée dans la nouvelle table, on sait à quel
endroit faire pointer le Call traité pour qu’il appel la bonne référence système. Et le tour est joué ! Dans la pratique, comme on
veut corriger .rdata et pas construire une nouvelle section .rdataBis, on récupère l’indice de la référence trouvé dans la nouvelle
table pour l’ajouter à l’adresse de début de .rdata. On fixe ainsi les call par rapport à .rdata, mais il reste encore à copier les
valeurs de notre nouvelle table dans .rdata pour que les call du programme soient valides.
- Etape 3 : Tout les call [.rdata] relogés on été modifiés, il ne reste plus qu’a copier la nouvelle table sur l’ancienne, et cette
protection ne sera plus qu’un mauvais souvenir (enfin presque...).
Une fois le patch réalisant les opérations ci-dessus lancé, on obtient bien maintenant :
0187:0062CC35 FF15ECB06300 CALL [KERNEL32!GetModuleHandleA] [ref : BFF77716]
et non plus:
0187:0062CC35 FF15ECB06300 CALL [KERNEL32!GetEnvironmentVariableA] [ref : BFFA1392]
Cette première protection est bien déplombée.
Une remarque, rien ne nous dit que le programme original avant d’être protégé avait la même table .rdata que notre nouvelle table,
mais cela n’a aucune importance car tous les appels sont fixés et ceci ne change donc rien au bon fonctionnement du programme. Il est
d’ailleurs probable que ce ne soit pas le cas (mais encore une fois, c’est sans importance).
Je devrais ici vous présenter le code du patch <Essais 3> qui est fonctionnel et qui retire bien la première protection, mais je
ne le ferai pas car si je continue à autant détailler et expliquer toutes mes patches, je vais écrire un livre. Je vais donc me
contenter d’expliquer les principes et résolutions des autres protections pour gagner un peu de place et vous trouverez le code des
trois première protections réunies dans un seul patch qui se trouve en annexe : ‘RemoveSafeDiscPatch v1.0 Part A’. Ce patch est
suffisamment commenté pour se passer d’une explication détaillée sur le fonctionnent du code. Encore une chose, la méthode que nous
venons de voir retire bien la première protection, mais ce patch seul ne fonctionnera pas car il est lié à une seconde protection,
il faudra donc retirer la protection un et deux dans le même temps.
Dans le source ‘RemoveSafeDiscPatch v1.0 Part A’ fourni en annexe, les fonctions dont nous venons de parler sont traitées dans les
procédures :
- BuildTable (Calcule la nouvelle table de référencement des appels système)
- CallRefFix (Corrige les Call[ref] en fonction de la nouvelle table de référencement)
- CopyTable (Copie la nouvelle table de référencement à la place de l'ancienne (dans .rdata))
Il reste à traiter trois protections, mais deux sont proches des techniques évoquées plus haut, dont une très similaire :
- JmpFix (Très similaire)
- CallFix
Commen?ons par traiter celle qui ressemble le plus au cas précédent.
7. Etude et solution pour les Call externes reroutés en JMP (Protection 2)
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
La seconde protection que nous allons à présent voir est liée à la première comme je l’ai déjà dit. Elle est gérée par la même
routine de calcul des références systèmes que nous avons vu précédemment, si bien que l’on aurait très bien pu considérer ces deux
protections comme une seule. La raison pour laquelle je distingue ces deux cas est que dans cette deuxième protection le code de la
section .text à été modifié alors que pour le cas précédent, c’était la section .rdata qui avait subi des modifications.
Comme pour la dernière fois, vous arriverez à repérer cette protection lors d’un crash. Une fois la protection précédente retirée,
il est légitime de faire un dump du programme et de lancer ce dump pour voir si notre patch marche bien et si il ne reste pas de
protection. Bien s?r, comme il reste des protections et comme les routines de sécurité de SafeDisc ne sons pas en mémoire, le jeu
crash. Le point d’origine du crash (facile à remonter) n’est d’ailleurs pas loin de l’OEP et c’est ce cas que nous allons étudier.
L’appel qui est à l’origine du crash se situe en 6273EA :
0187:006273AE E978600D00 JMP 006FD42B
Ce jump pointe sur la section stxt774 comme vous pouvez le constater en vous reportant à la table des sections. Après avoir tracé
un peu de code, on se rend compte que c’est encore ~DF394B qui est appelé au final, et si l’on regarde de plus près on retrouve notre
bonne vielle routine de calcul des références système, cette même routine que nous venons de patcher. Ceci signifie que ce JMP va en
fait appeler une routine système. Donc, le principe de ce système est comme précédemment de rerouter les Call [ref .rdata] vers une
routine de sécurité de C-Dilla, excepté que dans ce cas, le générateur de protection va transformer les Call [ref .rdata] en
Jmp <ref stxt774>. Il va donc falloir scruter le programme à la recherche de ces jump spécifique pour les remplacer par les
Call [ref .rdata] initiaux du programme avant sa protection par SafeDisc.
Voici l’illustration en code de notre exemple du JMP en 6273AE:
006273AE E978600D00 JMP 006FD42B --------|
|
006FD42B 53 PUSH EBX <-------|
006FD42C E893FCFFFF CALL 006FD0C4 ----------|
006FD431 B8F166A704 MOV EAX,04A766F1 |
006FD436 8D12 LEA EDX,[EDX] |
|
006FD0C4 870424 XCHG EAX,[ESP] <--------|
006FD0C7 9C PUSHFD
006FD0C8 058AFAFFFF ADD EAX,FFFFFA8A On se balade un peu dans stxt744...
006FD0CD 8B18 MOV EBX,[EAX]
006FD0CF 6BDB08 IMUL EBX,EBX,08
006FD0D2 035804 ADD EBX,[EAX+04]
006FD0D5 9D POPFD
006FD0D6 58 POP EAX
006FD0D7 EBA9 JMP 006FD082 -------|
|
006FD082 871C24 XCHG EBX,[ESP] <-----|
006FD085 C3 RET -----|
|
02BFF251 0000 ADD [EAX],AL <----|
02BFF253 68B4736200 PUSH 006273B4
02BFF258 68DC12EABF PUSH BFEA12DC
02BFF25D 9C PUSHFD
02BFF25E 60 PUSHAD
02BFF25F 54 PUSH ESP
02BFF260 6893F2BF02 PUSH 02BFF293
02BFF265 E8DFE04EFF CALL 020ED349 <<< Mais finalement, on revient en terrain connu :o)
02BFF26A 83C408 ADD ESP,08
02BFF26D 6A00 PUSH 00
02BFF26F 58 POP EAX
02BFF270 61 POPAD
02BFF271 9D POPFD
02BFF272 C3 RET
Pour casser cette seconde protection, on procède grosso modo de la même fa?on que précédemment. On patche la routine générale de
calcul des références systèmes pour qu’elle pose la référence de l’appel système original dans un registre et on la redirige vers
notre patch (veillez cette fois ci à bien modifier les deux cas de retour). Ensuite, la phase de scan de la section .text peut
commencer. Une fois que l’on est s?r d’avoir un Jmp valide (c'est-à-dire une référence d’appel système transformé en Jmp) on calcule
l’adresse de destination du jump et on s’y rend. Grace à notre modification, on va recueillir la référence originale et revenir à
notre patch. Il ne restera plus qu’à transformer le Jmp en Call [ref. rdata]. Pour calculer l’adresse de .rdata à pointer, on
cherche dans notre nouvelle table de référence système l’indice de la référence à appeler et on ajoute l’adresse de début de .rdata.
Si vous ne saisissez pas tout, lisez le code de la fonction JmpFix, ?a devrait s’éclaircir. Si on regarde a présent les valeurs
hexadécimales du code, il faut remplacer E9???????? (jmp <ref>) par FF15???????? (call [ref]). Vous noterez que le call [ref] prend
un byte de plus, mais ceci est pris en compte par le générateur de protection qui ne touche pas à ce byte lorsqu’il opère la
transformation (pas de changement de taille du code). Une fois que l’appel système a été effectué, l’adresse de retour ne se situe
pas une instruction après le jmp, mais une instruction plus un byte, ce qui correspond au cas du programme non protégé (call [ref]
original). Ahh, c’est tout de même bien fait :o) On pourra donc sans risque écraser le byte lorsque l’on refait le calcul pour notre
call et remettre les choses dans l’ordre.
Pour que le patch de cette protection soit effectif, il faut avoir copié la nouvelle table de références système sur l’ancienne
(dans .rdata). Aussi, les deux premières protections étant liées et il faut les faire successivement sans copier la nouvelle table
sur l’ancienne entre les deux patchs, sinon il serait impossible de retrouver les références système originales.
Voici le schéma à suivre :
call BuildTable ; 1) Calcule la nouvelle table de référencement des appels système.
call CallRefFix ; 2) Corrige les Call[ref] en fonction de la nouvelle table de référencement.
call JmpFix ; 3) Convertit les jmp <ref> en Call[ref] avec la nouvelle table de référencement.
call CopyTable ; 4) Copie la nouvelle table de référencement à la place de l'ancienne (dans .rdata).
Une fois que l’on a fait tourner notre patch, à la place du
006273AE E978600D00 JMP 006FD42B
on obtiendra le call [.rdata] correspondant :
006273AE FF1520B16300 CALL [KERNEL32!GetCommandLineA]
Une petite remarque au passage concernant le code du patch fonctionnel (RemoveSafeDiscPatch v1.0 Part A) : Plut?t que de modifier
manuellement à chaque fois la routine générale de calcul des références système pour ajuster le jmp qui pointe vers l’adresse de
retour de notre patch, il est plus intéressant d’écrire un petit bout de code qui fasse ce travail automatiquement. C’est surtout
avantageux lorsque l’on débugue le patch, on gagne ainsi beaucoup de temps. A noter que les modifications effectuées se font dans
un segment de mémoire en lecture seule :
~DF394B .txt 0003 0187:020EC000 00001E02 CODE RO
Il faut donc, avant de lancer le patch, modifier les attributs de cette zone à chaud avec SoftIce pour rajouter les droits en
écriture. On peut bien s?r automatiser cette procédure avec un peu de code en utilisant l'API VirtualProtect, ou encore faire le
calcul des pages tables et modifier le second bit de la page concerné, mais ceci sort du cadre de ce tutorial. Avec IceDump,
utiliser simplement la commande /PAGEFLAG <addr> PWUC.
Les protections 1 et 2 sur les relogements d’appels systèmes externes sont maintenant out. Vous pouvez faire un dump du programme
et l’utiliser par la suite comme backup. Avec IceDump, procédé comme suit : (On peut aussi utiliser LordPE avec un jmp eip à l’OEP et
corriger par la suite le EB FE en 55 8B à l’aide d’un éditeur hexadécimal. Si vous utiliser IceDump, penser à désactiver les
breapkpoints qui pointent sur du code à dumper, sinon vous allez vous retrouver avec des int3 (CC) dans le ficher du dump aux
endroits des bpx.)
/dump 400000 30225c c:\tmp\Game_12.bin
Pour reprendre le crack ou vous l’avez laissé, lancer le jeu (l’original, avec CD dans le lecteur) et une fois arrivé à l’OEP sous
SoftIce (toujours le même bpx 62733A) charger l’image que vous avez faite juste avant :
/load 400000 30225c c:\tmp\Game_12.bin
Mise en garde : Le RemoveSafeDiscPatch propose de s’occuper des trois premières protections à la suite si l’on regarde le code,
mais il faut auparavant lancer la routine ‘TableCallSkip’ et quitter le jeu pour que cela fonctionne. Si vous partez de l’image
Game_12.bin pour continuer sur la troisième protection, vous pourrez utiliser le même code mais il ne faudra exécuter que les
fonctions ‘TableCallSkip’ (puis quitter ou crasher) et ensuite ‘CallFix’ (Ces routines et leur fonctionnement sont traités dans le
chapitre qui suit).
Voila, on peut à présent passer à la troisième protection, on attaque les choses sérieuses à présent ! Nan, je plaisante, elle est
toute simple à cracker, d’ailleurs il n’y a rien de vraiment difficile à cracker dans SafeDisc.
8. Etude et solution pour les Call internes rerouté (Protection 3)
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Encore une fois, vous découvrirez cette protection comme vous avez trouvez les autres, c'est-à-dire en observant les crashes. Les
premiers effets de cette troisième protection se trouvent aussi non loin de l’OEP. Je passe sur la mise en évidence du problème pour
expliquer directement ce que fait la protection (vous devez commencer à conna?tre la musique). Il s’agit pour cette fois d’un Call
interne rerouté en call interne (403389). Ce call interne va à son tour appeler une routine de sécurité de SafeDisc (une autre cette
fois) pour calculer la référence originale de l’appel interne.
Voici le premier exemple trouvé :
00627394 E8F0BFDDFF CALL 00403389
qui, une fois passées les routines de calculs de SafeDisc nous amène en : 62CDA0. (Le Call original était donc un call 62CDA0.)
Si vous étudiez plusieurs cas, vous remarquerez que l’appel de remplacement pointe toujours sur la même routine: CALL 00403389.
Maintenant, si l’on rentre dans cette fonction et que l’on trace un peu de code, on arrivera finalement dans ~DF394B que l’on conna?t
bien. Attention toutefois, ce n’est pas la même fonction de calcul des références originale que précédemment (Il faut bien changer un
peu, sinon ?a devient monotone :o).
Voici un petit extrait de ce que l’on trouve sous ces fameux CALL 00403389 :
00403389 51 PUSH ECX
0040338A 50 PUSH EAX
0040338B E854FCFFFF CALL 00402FE4 ----------
... |
|
00402FE4 B83B950000 MOV EAX,0000953B <--
00402FE9 59 POP ECX
00402FEA 03C1 ADD EAX,ECX
00402FEC 8B00 MOV EAX,[EAX]
00402FEE FFE0 JMP EAX ----------
... |
|
020AFF1E 58 POP EAX <---------
020AFF1F 59 POP ECX
020AFF20 6800004000 PUSH 00400000
020AFF25 9C PUSHFD
020AFF26 60 PUSHAD
020AFF27 8B442428 MOV EAX,[ESP+28]
020AFF2B 8BCC MOV ECX,ESP
020AFF2D 83C124 ADD ECX,24
020AFF30 50 PUSH EAX
020AFF31 51 PUSH ECX
(H) 020AFF32 E8D2FFFFFF CALL 020AFF09 <<< L’autre fonction de calcul des références originales.
(I) 020AFF37 59 POP ECX (pour les références internes)
020AFF38 58 POP EAX
020AFF39 61 POPAD
020AFF3A 9D POPFD
020AFF3B C3 RET
Toujours avec la même logique, devinez ce que l’on va faire ? Modifier la routine ci-dessus bien s?r afin de récupérer la valeur de
l’appel interne original et revenir a notre programme. On va donc placer notre jump en (I) et restaurer le code que l’on a écrasé
avec notre jump une fois arrivé dans notre programme. A la place du RET on posera un pop edx pour disposer de la valeur de l’appel
interne original et le tour est joué.
Voici que l’on obtient après modification:
020AFF32 E8D2FFFFFF CALL 020AFF09
(I2)020AFF37 E931243481 JMP 833F236D
Et il faut bien s?r réécrire le code écrasé par le JMP. Ceci se fait dans notre patch :
833F236D 59 POP ECX
833F236E 58 POP EAX
833F236F 61 POPAD
833F2370 9D POPFD
833F2371 59 POP ECX
833F2372 EBE2 JMP 833F2356
Quelques explications sur le fonctionnement du patch pour cette troisième protection :
On scan la section .text (qui contient le code du jeu) à la recherche de call 403389. Mais attention, pour la recherche de cette
cha?ne, il faut savoir que la référence du Call <ref> n’est pas encodée statiquement, mais par rapport à la position de ce call
(comme un jmp). C'est à dire que les quatre bytes qui suivent E8 sont donnés par rapport à la position de ce call+5 (adresse de
retour du call). On va donc balayer le programme en cherchant ‘E8’ et une fois ce byte repéré, on prendra les quatre bytes suivant
pour calculer dynamiquement la référence pointé. Si la référence calculée est égale à 403389, alors nous avons trouvé notre call.
Cette méthode n’est pas parfaite, car on peut très bien tomber entre deux instructions et croire avoir trouvé un call 403389 alors
qu’il n’en est rien. Pour éviter ceci, il faudrait utiliser une fonction ‘IsValidInstruction’ pour s’assurer que l’on n’est pas tombé
entre deux instructions lors de notre scan. Cependant, les probabilités pour tomber sur un cas de call 403389 dans un programme de
moins de 3 Mo sont très faibles et cette simple vérification est amplement suffisante.
Poursuivons, une fois un call repéré, on va sauter sur la routine 403389 en mettant auparavant l’adresse de retour du call traité
dans la pile. C’est un fake comme on l’a déjà fait pour la protection précédente. Une fois le calcul de la valeur effectué par la
routine de SafeDisc, on va revenir à notre patch grace à la modification apporté en (I2). (Remarque : Cette procédure de
modification se fait automatiquement dans le patch final RemoveSafeDiscPatch v1.0 Part A.)
Une fois le patch exécuté, la troisième protection devrait être enlevée. Mais voila, dans la série des bonne surprises, on a un
cas spécial de call qui fout la merde. Il se trouve qu’il existe dans le programme des CALL 403389 qui, une fois reroutés, pointent
sur une instruction plus loin. Voici un exemple :
0062DFDB E8A953DDFF CALL 00403389
En effet, lorsque l’on regarde où l’on arrive après avoir passé les routines de SafeDisc, on trouve la valeur 0062DFE0, qui est
en fait l’adresse le l’instruction suivante. C’est parfaitement débile, mais c’est surtout problématique dans la mesure où ?a
perturbe la routine de calcul des références des appels internes. En effet, si on lance la routine de calcul avec cette adresse
puis qu’on l’utilise ensuite sur une autre adresse valide (de call 403389), la référence de ce deuxième traitement va être erronée.
En fait la référence ne va pas être totalement fausse, mais décalée.
Je reprends, lorsque l’on appelle un call 403389 qui pointera sur eip+5 (je l’appellerais désormais ‘call débile’), toutes les
références ultérieurement calculées seront décalé d’un valeur n qui varie en fonction des appels. Ce que je viens de dire est
partiellement faux, car, si toutes les références des call 403389 valides seront modifiées, en revanche les références des call
débiles restent bonnes (elle pointent toujours à eip+5). Effectivement, la routine de SafeDisc arrive toujours à déterminer les
références des call débiles :o) C’est important car c’est ce qui va nous permettre de corriger le problème.
Effectivement, on peut corriger sans problème tout les call 403389 si on sait éviter les call débile pour ne pas créer de
décalages. Il va donc falloir créer un tableau des call débiles à éviter. Le problème, c’est que l’on ne peux pas savoir à l’avance
si un call est débile ou pas (pris hors contexte, ce tutorial serait vraiment très naze, alors svp, ne me citez pas :o) Pour savoir
si un call est débile, on est obliger d’aller voir (ok, ok j’en rajoute exprès un petit peu ;). Si la référence obtenue vaut eip+5
(instruction suivant le call traité), on la rajoute dans la ‘BlackList’. Bien sur, une valeur de décalage est ajoutée, mais ceci ne
concerne pas nos call débiles. Une fois la liste construite, il faudra relancer le jeu pour ne pas avoir le problème de décalage. On
pourra alors corriger tout les call 403389 en évitant les call débiles. Je reconnais que cette méthode est loin d’être propre et il
doit exister une meilleure solution, mais elle marche et je n’ai pas creusé beaucoup plus (vu autrement, c’est un bon rapport
crack/temps).
En fait, si vous avez fait quelques tests, vous avez d? remarquerez une chose, les call débiles sont toujours précédés d'un RET.
Ceci pourrait être une heuristique pour les repérer, mais à elle seule, cette technique de repérage est un peu légère et il se peut
que l'on se retrouve avec des cas de call internes non traités. Comme je n'ai pas essayé cette méthode, je ne m'avancerais pas plus.
Vous pouvez toujours essayer cette voie pour voir où ?a mène. J'ai aussi noté quelques autres étrangetés dans le programme comme des
CALL 403387, ce qui ne pointe pas sur une instruction valide. Ces appels ne semblent cependant jamais être exécuté.
Bon, tout ?a c'est bien beau, mais que faire des call débiles ? C’est simple, puisqu’ils pointent sur une instruction plus loin et
qu’on ne peux pas les laisser (car ils appellent la routine de SafeDisc), on va simplement les nopper. Cinq petits nop pour un call
débile, emballez c’est pesé.
Si je reprends le fonctionnement du patch pour cette troisième protection, il faut d’abord commencer par appeler une routine
spécifique de notre patch qui va construire la ‘BlackList’. Il s’agit de la routine ‘TableCallSkip’. Puis, killer et relancer le jeu
pour enfin appeler la routine ‘CallFix’ qui va proprement retirer la protection 3. Si vous désirez faire les trois premières
protections d’un seul coup, il faut donc auparavant lancer ‘TableCallSkip’ et killer le jeu.
Si tout s’est bien passé, le call:
00627394 E8F0BFDDFF CALL 00403389
doit être remplacé par:
00627394 E8075A0000 CALL 0062CDA0
Voici un petit extrait de la BlackList pour vous donner une idée du nombre de call débiles à éviter :
833F4000: 0040CC3B 0040F2EB 004108AB 0041093B
833F4010: 004109CB 0041245B 0041278B 0042537B
833F4020: 00425FBB 0042FBCB 0043271B 004335DB
833F4030: 0043A25B 0043ABEB 0043B0FB 0043BFDB
.
.
.
833F4270: 006064FB 00606DBB 00607F4B 006089EB
833F4280: 0060B17B 0060CB7B 00610C6B 0061155B
833F4290: 0061680B 006184DB 0061DF4B 0061E62B
833F42A0: 006245AB 0062DFDB 00000000 00000000
On peut se demander si ces call débiles sont une forme de protection ou pas. Le générateur de protection a-t-il volontairement
créé ces call dans le but de nous tromper ou est-ce seulement un effet de bord de la protection ? Personnellement, je ne pense pas
que ce soit une protection, mais si c’en est une, c’est vraiment une protection débile ;o)
Si vous voulez faire des essais manuellement pour tester cette protection, voici quelques cas que j’ai repéré :
Cas valide de call 403389 :
627394 CALL 403389 -> 62CDA0
62D4A4 CALL 403389 -> 626B03
Maintenant l’appel qui cause le premier crash si l’on ne saute pas les call débiles (toujours dans les cas valides) :
54976B CALL 403389 -> 549F00 (Référence originale, ou si l’on a évité les call débiles précédents)
-> 54AF6C (Référence erronée, si l’on a n’a pas évité les call débiles précédents)
Cas non valide (ou encore call débiles):
62DFDB CALL 403389 -> 62DFE0 (cause un décalage de +32)
(et l’extrait de la BlackList est suffisant pour que je ne m’étende pas...)
Voila pour cette troisième protection dont vous trouverez le code en annexe dans ‘RemoveSafeDiscPatch v1.0 Part A’. Il ne nous
reste plus qu’à attaquer la dernière. Mais avant de vous lancer, faite un dump du jeu et appelez le au hasard ‘Game_123.bin’. Cette
image nous servira de backup, mais elle nous servira aussi pour reconstruire le futur Game_1234.bin. Procédez également à quelques
tests : Démarrer le jeux original, charger le dump en mémoire et vérifier que :
- les Call [.rdata] apparaissent bien avec leur nom d’équivalence (ex: 627360: CALL [KERNEL32!GetVersion])
- les Jmp que nous avons traités ont bien disparu pour laisser la place à des Call [.rdara] (ex: 006273AE :
CALL [KERNEL32!GetCommandLineA]
- les appels internes relogés on été modifiés (ex: 627394: CALL 0062CDA0)
Et bien s?r que le jeu patché (123) fonctionne bien.
Nous pouvons à présent aborder la dernière protection, retenez votre souffle... Pour pousser un gros soupir car y’a vraiment rien
de dur.
9. Etude et solution pour le code modifié à la volée (Protection 4)
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Cette quatrième et dernière protection anti-dumping est a mon avis la plus intéressante à analyser. Il s’agit d’une petite astuce
permettant de rediriger l’eip du programme sur une routine de SafeDisc. Ce joli petit système de capture va lancer la routine de
protection quand il va rencontrer l’interruption 3 par exemple. Mais l’interruption 3 n’est pas la seule instruction à être utilisée
et il y a toute une panoplie d’instructions qui sont mises à profit pour rediriger l’exécution du programme.
Si l’on regarde le fonctionnement de cette protection, on découvre qu’une fois que ces instructions spécifiques sont exécutées et
que la routine de sécurité a finit son travail, le programme revient au même point ! (et non pas une instruction plus loin) Et aussi
que l’instruction spécifique de déclenchement s’est transformée comme par magie en une autre instruction. L’instruction de
déclenchement a donc été elle même être modifié, et cette instruction de remplacement est en fait l’instruction du programme
original (non modifié par le générateur de protection).
Le générateur de protection va donc choisir certaines instructions (au hasard peut-être) pour les remplacer par des instructions
spécifiques de déclenchement (comme l’INT 3). Lorsque le programme passe sur ces instructions, il va exécuter la routine de
protection qui va remettre les choses en ordre puis revenir au même point, le programme peut alors continuer normalement. On peut
également noter que ces modifications sont définitives. Lors de son lancement, le jeu contient beaucoup de code à modifier à la
volée, et au fur et a mesure de son utilisation, les modifications à faire se font moindre.
On peut donc tout de suite penser à faire un dump de la section .text après une ou deux heures de jeux, mais c’est une très
mauvaise idée, car on oubliera toujours quelques cas que l’on finira t?t ou tard par se prendre en pleine tête...
J’ai assez parlé, voyons tout de suite quelques exemples de ces fameuses modifications :
- Pour commencer, un cas de SGDT :
005920FD E8522C0900 CALL 00624D54
00592102 >0F01C4< SGDT ESP
00592105 A3A4F86E00 MOV [006EF8A4],EAX
qui une fois modifié donnera le code suivant:
005920FD E8522C0900 CALL 00624D54
00592102 >83C404< ADD ESP,04
00592105 A3A4F86E00 MOV [006EF8A4],EAX
- Cas de RSM (qui fout la merde dans le moteur de désassemblage de SoftIce)
0055CD30 E8B17F0C00 CALL 00624CE6
0055CD35 >0FAA RSM
0055CD37 DB4B ESC
0055CD39 3B<F7 CMP ESI,EDI
0055CD3B E3C1 JECXZ 0055CCFE
0055CD3D EA038956085F5E JMP 5E5F:08568903
0055CD44 5B POP EBX
qui une fois modifié donnera le code suivant: (5 bytes modifiés)
0055CD30 E8B17F0C00 CALL 00624CE6
0055CD35 >B8398EE338< MOV EAX,38E38E39
0055CD3A F7E3 MUL EBX
0055CD3C C1EA03 SHR EDX,03
0055CD3F 895608 MOV [ESI+08],EDX
0055CD42 5F POP EDI
0055CD43 5E POP ESI
0055CD44 5B POP EBX
- Encore un autre cas, sur l’INT 3 cette fois :
004713A2 E859331B00 CALL 00624700
004713A7 >CC INT 3
004713A8 CC INT 3
004713A9 CC< INT 3
004713AA 83EC0C SUB ESP,0C
qui une fois modifié donnera le code suivant:
004713A2 E859331B00 CALL 00624700
004713A7 >8D4D20< LEA ECX,[EBP+20]
004713AA 83EC0C SUB ESP,0C
Regardons à présent la routine qui est appelée pour faire le travail de substitution :
020BAEC4 9C PUSHFD <<< Point d’entrée dans la routine (à l'exécution de RSM par ex.)
020BAEC5 57 PUSH EDI (Intéressant à monitorer)
020BAEC6 56 PUSH ESI
020BAEC7 52 PUSH EDX
020BAEC8 51 PUSH ECX
020BAEC9 53 PUSH EBX
020BAECA 50 PUSH EAX
020BAECB E8B4FDFFFF CALL 020BAC84
020BAED0 58 POP EAX
020BAED1 5B POP EBX
020BAED2 59 POP ECX
020BAED3 5A POP EDX
020BAED4 5E POP ESI
020BAED5 5F POP EDI
020BAED6 9D POPFD
020BAED7 8D64240C LEA ESP,[ESP+0C]
020BAEDB FF6424FC JMP [ESP-04] <<< Retour a l’adresse de l’instruction en cours
Il est intéressant de monitorer cette routine pour voir quelles sont les instructions qui causent son appel. Un fois que l’on a
traité une petite série de cas, on peut essayer de trouver une généralisation. (Note : pour voir directement le code de l’instruction
de déclenchement sous SoftIce, utiliser la macro suivante :
macro VIEW = ‘d @(esp+8); u @(esp+8);’
avec un: bpx 020BAEC4 do ‘VIEW’)
Voici les instructions utilisées que j’ai relevées :
SGDT EAX / SGDT ECX / SGDT EDX / SGDT EBP / SGDT ESP
SIDT EAX / SIDT ECX / SIDT EDX / SIDT EBP / SIDT EDI
RSM / UD2 / INT 3
Voici la généralisation que j’ai trouvé pour procéder à la recherche hexadécimale dans le code du programme :
SGDT & SIDT -> 0F 01 C?
RSM -> 0F AA
UD2 -> 0F 0B
INT 3 -> CC
Il va en effet falloir parcourir l’ensemble de la section .text pour chercher toutes les occurrences que le générateur de
protection a généré. Une fois que l’on a détecté un cas, on saute dessus pour laisser la transformation s’opérer toute seule et on
revient à notre patch en modifiant au préalable la fin de la fonction 020BAEC4.
Mais ici, il se pose un problème majeur, les cha?nes recherchées sont très courtes, et les cas invalides sont nombreux. Un simple
‘MOV EAX, 0FAA’ sera détecté comme cha?ne à modifier. Heureusement pour nous, les gens de chez C-Dilla écrivent des routines super
propres avec beaucoup de vérifications, si bien que si on lance le traitement d’un cas erroné, la routine ne fera aucune modification.
Donc, on va en user et en abuser :o) Bon, on ne va pas trop pousser le bouchon quand même en lui passant tout le code... On va lui
faciliter un peu le travail en effectuant un premier filtrage, c’est à dire en cherchant les cha?nes 0F01C? / 0FAA / 0F0B / CC.
Comme depuis toujours, on va utiliser le force de l’adversaire (ou plut?t l’énergie) comme dans je ne sais plus quel art martial.
Sauf que ici, on réutilise le code qui a déjà été écrit à notre avantage. Comme le jeu fonctionne, il y a forcement quelque part une
fonction qui fait le travail, tout ce que l’on a à faire c’est de la localier et de trouver à quel moment il est plus simple de l’
appeler pour faire le travail. Nul besoin de reverser toutes les fonctions, il faut juste un peu d’observation et le tour est joué.
Si vous avez compris ?a, vous avez tout compris ! De plus, on est programmeur, et un programmeur c’est fainéant, alors si on peut en
faire le moins possible, on ne va pas se gêner =)
Sur ce coup là, on a eu beaucoup de chances car les fonctions étaient propres, ce qui nous a fait gagner du temps. Si ce n’avait
pas été le cas, il aurait fallu écrire une fonction ‘IsValidInstruction’ pour vérifier que l’on est bien sur une instruction et pas
entre deux. Peut-être que les futures version de SafeDisc ne seront pas aussi ‘Safe’, alors à titre d’exercice pour la prochaine
fois, écrivez moi cette fonction :)
Une petite remarque en passant, les quelques valeurs hexadécimales qui sont après l’instruction de déclenchement n’ont aucune
relation avec l’instruction de remplacement, ce sont en quelque sorte des octets de bourrage.
Voyons maintenant l’utilisation du dernier patch car il est un peu particulier. Vous noterez qu’il été mis a part dans
‘RemoveSafeDiscPatch v1.0 Part B’ et ce n’est pas pour rien. En effet, il faut faire un dump du programme entre la part A et la part
B car pour lancer le second patch (part B), car on aura besoin du jeu en cours d’exécution pour pouvoir réaliser le second patch. Le
programme sera donc déjà initialisé et l’image mémoire ne sera plus valide. Il faudra reconstruire le jeu cracké à partir du dump
intermédiaire et de la nouvelle section .text patchée.
Avant de lancer le ‘RemoveSafeDiscPatch v1.0 Part B’ il faut aussi que la routine de protection 20BAEC4 soit dans un état
permettant sa bonne exécution, ce qui n’est pas le cas à l’OEP. Pour permettre le fonctionnement du patch, l’astuce consiste à lancer
le jeu en posant un breakpoint à 20BAEC4, et une fois revenus de la routine de protection dans le jeu (traitement du premier cas en
55CD35), on change l’eip pour la faire pointer sur notre patch B. C’est même en fait un peu plus compliqué que cela, car on distingue
deux cas :
- les SGDT / SIDT / RSM / UD2
- les INT 3
Il faut traiter ces deux cas en fonction de leur contexte respectif. Si l’on s’arrête en 55CD30 (permier cas), on pourra traiter
tout les SGDT/SIDT/RSM/UD2 (bref 0F ?? ??) mais pas les INT 3. On va donc pour cette fois lancer les routines : ReconstructP1,
ReconstructP2 et ReconstructP3. Il reste alors le cas des INT 3 à traiter. Pour cela, il faudra laisser continuer le programme et l’
interrompre sur un cas valide de CC. Comme il ne restera plus que ces cas, un simple breakpoint en 20BAEC4 suffira. Une fois ce cas
traité et de retour dans le code du jeu, on pourra alors lancer la dernière routine du patch qui s’occupe des INT 3. Voila pour l’
utilisation du patch B qui est je vous l’accorde, un peu tordue. Pour tout traiter d’un seul bloc, il faudrait pousser plus loin les
recherches et un peu reverser les fonctions. Si vous avez le temps, pourquoi pas...
Je ne détaille pas le fonctionnement du programme, si vous êtes arrivé jusqu’ici, vous vous doutez de la manière dont il procède,
et si ce n’est pas le cas, jetez un coup d’oeil au code.
10. Fix du header et reconstruction de la table des importations
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
On dispose à présent d’un joli dump du jeu où la protection de C-Dilla est retirée. Il va donc falloir rendre ce dump fonctionnel
et ce sur toutes les machines. Commen?ons simplement, nous allons tout d’abord le faire fonctionner sur notre PC. Pour ?a, il va
tout d’abord falloir changer l’EP du programme qui est resté sur l’EP du loader. A l’aide de LordPE ou de ProcDump, changer donc
l’EP du dump pour le mettre à l’OEP moins l’ImageBase, soit en 22733A (62733A - 400000) pour le jeu Mafia. A ce point, si vous
renommez le dump en .exe et si vous lancez le jeu, tout se passera bien jusqu’au premier appel de librairie externe que l’on n’aura
pas rerouté (LS3DF.dll par exemple). Le jeu crashera alors car cette librairie n’a pas été chargée lors du démarrage du jeu. Ceci est
parfaitement normal car la table des importations est naze. (Les appels à kernel32, user32 et advapi32 fonctionnent bien quant à eux
seulement parce que leurs références sont encodées statiquement (par notre patch) et que ces dll sont toujours en mémoire.)
Bon, revenons à notre problème, on va à présent reconstruire la table des importations pour rendre notre jeu cracké un peu plus
portable. Là encore, il y a plusieurs solutions, La solution root’s qui consiste faire la moitié du travail en réajustant
manuellement les pointeurs de la table des importations pour ensuite appeler un rebuilder qui reconstruit le ‘hint name array’ et
les noms des fonctions correspondantes à partir des adresses physiques des fonctions (encodé statiquement par notre patch) encore
appelé ‘import adress table’. Je ré-explique le problème pour que vous saisissez bien. Dans notre dump, la table des importations
contient les adresses statiques pour les dll kernel32, user32 et advapi32. Le programme fonctionne donc bien sur notre machine, mais
pas sur une autre plate-forme car ces adresses statiques changent d’une version à l’autre. Pour permettre la compatibilité, en temps
normal, le loader reconstruit ces adresses statiques à partir des noms de fonctions (ex : GetVersion). Or dans notre dump, ces
informations sont manquantes. Il va donc falloir les reconstruire à partir des adresses statiques, et on va utiliser pour cela un
rebuilder (sauf si vous avez envie de vous prendre la tête à chercher et ajuster 100 références...).
Bref, dans tout les cas on aura besoin d’un besoin d’un programme de reconstruction de la table des importations, alors autant
choisir le méthode la plus simple et la plus rapide. On va donc opter pour la solution toute automatisée, pour les gros fainéants
comme moi (je le rappelle, moins j’en fais, mieux je me porte :o)
Pour cette solution, j’utilise ‘Import REConstructor v1.3’ de MackT (UCF). Ce petit programme est parfait pour ce que l’on veut
faire. Voici la marche à suivre : Lancer le jeu original (avec le CD dans le lecteur) et une fois arrivé à l’OEP, charger le dump
Game_1234.bin (avec l’OEP modifié, ?a sera déjà fait pour la suite comme ?a). Vous pouvez alors laisser le jeu s’exécuter. Switchez
ensuite sur ImportREC et sélectionnez le processus cible. Lancez un IAT AutoSearch puis un GetImport. Vous devriez apercevoir toutes
les dll (et appels de fonctions) prise en compte par le jeu. Il ne reste plus qu’à lancer un FixDump en sélectionnant le fichier de
votre dump (peu importe si ce n’est pas un exécutable). ImportREC va se servir de ce fichier pour en construire un nouveau en fixant
la table des importations. Le dump fixé porte un ‘_’ en plus à la fin de son nom). Voila voila, la table des importations de notre
dump est fixé automatiquement en moins de 30 secondes. Merci au passage à MackT et à UCF pour leurs sympathiques proggies.
Bon, dernière modification, on va à présent reconstruire ce dump avec LordPE (ou ProcDump). Pour LordPE veillez à ce que dans les
options du rebuilder les options ‘Dumpfix’ et ‘ValidatePE’ soient bien activées. Vous pouvez aussi sélectionner un réalignement du
dump, votre dump n’en sera que plus petit. Vous pouvez alors lancer ‘Rebuild PE’ et admirer le travail. Votre exécutable est plus
petit, il a retrouvé sa belle ic?ne et surtout, il fonctionne, sans les routines de protection de SafeDisc et sur toutes les
plates-formes. C’est gagné !
Notez que le crack fixé contient à présent une section de plus que ImportREC à rajouter. Si vous trouvez que ?a ne fait pas propre,
vous pouvez toujours repatcher le fix, pour recaser les modifications dans .rdata. Mais dans tout les cas, penser à remercier les
auteurs des outils que vous utiliser, sinon ce n’est pas très fair play.
Autre alternative, plut?t que de passer par un rebuilder externe, on peut aussi utiliser IceDump (commande /PEDUMP), celui-ci
inclus le moteur Phoenix écrit par G-Rom. On retrouve ainsi les très familières options de ProcDump en ce qui concerne la partie
de reconstruction de la table des importations (/option P).
11. Patch du CD-Check spécifique au jeu
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Pour un peu, je l’aurais oublié... La plus grosse difficulté de ce crack :o) Si vous lancez votre crack fixé flambant neuf sur une
autre bécane, vous aurez droit au message suivant : ‘Please insert MAFIA CD 1’ ce qui ma valu un fou rire à me décrocher la machoire.
(bahh, oui, le CD est pas dans le lecteur vu que j’ai changé de PC...) Après la protection SafeDisc, un CD-Check bidon de chez bidon !
En fait ?a devrait être la protection du jeu avant que Illusion Softworks ne fasse appel à C-Dilla pour protéger leur produit, et
ils ont du oublié ou avoir la flemme de l’enlever. Enfin tout ceci était bien dr?le :) Je vous file tout de même une copie d’écran
pour vous monter à quoi ?a ressemble :
00624490 56 PUSH ESI
00624491 8B350CB26300 MOV ESI,[USER32!MessageBoxA]
00624497 E864000000 CALL 00624500
0062449C 84C0 TEST AL,AL
0062449E 7525 JNZ 006244C5 <<< Il était vraiment très dur à trouver...
006244A0 A128926F00 MOV EAX,[006F9228] Jumpez moi ?a fissa et on en parle plus.
006244A5 85C0 TEST EAX,EAX
006244A7 7406 JZ 006244AF
006244A9 8B08 MOV ECX,[EAX]
006244AB 50 PUSH EAX
006244AC FF513C CALL [ECX+3C]
006244AF 6A15 PUSH 15
006244B1 6A00 PUSH 00
006244B3 68F8176500 PUSH 006517F8
006244B8 6A00 PUSH 00
006244BA FFD6 CALL ESI
006244BC 83F802 CMP EAX,02
006244BF 75D6 JNZ 00624497
006244C1 32C0 XOR AL,AL
006244C3 5E POP ESI
006244C4 C3 RET
006244C5 B001 MOV AL,01
006244C7 5E POP ESI
006244C8 C3 RET
Lorsque l’on termine là dessus, on se dit que vraiment, c’était pas dur ;)
Pas de mystères, pas de magie, juste de la logique ! Pour moi, c’est ?a le cracking. Vous remarquerez aussi que en plus du fait que
le jeu se lance plus vite au démarrage, le jeu cracké doit logiquement fonctionner un peu plus rapidement que le jeu protégé du fait
qu’il n’y ait plus de routines de sécurité à exécuter. Moralité, cracker donc vos jeux avant d’y jouer, vous gagnerez du temps au
démarrage et quelques FPS ;) Pour terminer, vous pouvez écrire un remover automatique si vous êtes motivé, il y a quelques
difficultés intéressantes qui vous attendent.
12. Conclusion et perspectives
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Nous voilà arrivés au terme de ce tutorial, et si vous avez pu apprendre quelque chose, j’en serais heureux. La protection de
C-Dilla est certes longue à déplomber, mais il n’y a rien de difficile si l’on sait exploiter les fonctions de gestion de la
protection qu’ils ont écrit. On pourrait d’ailleurs imaginer des systèmes anti-dumping bien plus complexes et plus difficiles à
déplomber que ceux que l’on vient de trouver. Par exemple, la difficulté augmenterait si le reroutage des fonctions interne et
externe ne passait plus par une seule fonction mais par des centaines. Plus difficile certes, mais rien d’impossible puisqu’il
suffit d’écrire un patcheur de fonction automatique qui identifie ces fonctions et les modifie. Une protection toujours plus
intéressante serait alors d’inclure du polymorphisme dans ces centaines de fonctions de sorte qu’elles soient encore plus dures à
repérer (je rappelle qu’il faut revenir à notre patcher dans tout les cas pour notre méthode). Mais encore en fois, rien
d’impossible puisque que l’on peut écrire un scan traceur qui monitore l’esp. Concernant la modification du code à la volée, les
gens de C-Dilla auraient pu faire mieux, car ils ne font que remplacer du code invalide par du code valide. Ils auraient pu faire
un double processus, d’une part reconstruire à l’eip et d’un autre coté ‘brouiller’ ailleurs pour que l’on ne puisse jamais avoir
une image mémoire valide. Enfin, ne jamais dire jamais car il suffit tout simplement de court-circuiter la partie de la routine qui
détruit le code ailleurs, la seule difficulté étant alors de trouver cet endroit (le reverse-engenring, c’est fait pour ?a). Plus
complexe maintenant, imaginons un système qui fasse tenir plusieurs routines de taille n dans un seul espace mémoire de taille n en
les substituant chacune à leur tour de sorte que toutes les routines ne soient jamais en mémoire. Ca c’est un problème plus
difficile à résoudre, mais à mettre en place aussi puisqu’il ne faut pas que ces routines se fasse mutuellement référence, ce qui
revient a la résolution d’un problème d’atteignabilité dans un graphes. Et ce n’est pas aussi simple à résoudre sur un programme qui
pèse plusieurs mégas. De ce coté on est tranquille (des exemples jouet peuvent tout de même fonctionner) et on a le temps de voir
venir. Tout ceci me rappelle la glorieuse époque Atari/Amiga :) Ahhhhhh, je revis ! Enfin, après tant d’année de médiocrité dans les
protections commerciales, ?a commence à devenir intéressant. J’ai déjà hate de voir les prochaines. (A noter qu’il existe de très bon
CrackMe ingénieux qui dépassent de loin les protections commerciales)
Tout ceci me fait penser à un truc, il existe des CrackMe, des ReverseMe, des KeyGenMe... Et si l’on créait des DumpMe ?! On
pourrait facilement brouter les protections commerciales en terme de complexité de technique anti-dump et leur mettre 10km dans la
vue :) Au moment ou j’écrit ces lignes, je n’en ais pas encore vu qui propose ce concept (en revanche, certains CrackMe incluent
déjà des protections anti-dumping en empêchant par exemple une simple lecture de leurs zone mémoire pour les dumper qui passant par
une API windows). Alors si ?a vous chante, Let’s code !
13. Avertissement, notes de l’auteur
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Si vous vous attendiez ici un disclaimer, c’est pas gagné... J’ai pas envie de raconter le baratin habituel : attention...
machin... c’est pas bien... à vos risque et périls... fourni uniquement dans un but pédagogique... l’auteur dégage tout
responsabilité... n’encourage personne à outre passer la loi... patati et patata...
Je veux juste préciser une chose, j’ai cracké je jeu Mafia certes, mais j’ai acheté ce jeu ! Oui, j’ai payé pour jouer, et je
joue ! J’ai pris plaisir à finir le jeu, et comme quelque part, j’ai aussi payé la protection pour le jeu, je m’amuse avec. Normal
non ?! :o) Cet tutorial n’est rien d’autre qu’une aide pour ceux qui veulent aussi s’amuser comme moi. Voilà, je suis donc un
crackeur moral ;) Et je ne considère pas que ce que je fais soit illégal puisque je récompense les auteurs de software en achetant
leurs produits, et je ne diffuse aucun crack qui réduirait leurs profits. Tout ce que je diffuse, c’est du savoir, des connaissances.
Libre à vous d’en faire ce que vous voudrez.
Peex
14. Remerciements
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
Je tiens à remercier :
Yoda, pour ses excellents programmes ainsi que pour ses explications.
Toute l'équipe qui a con?u et réalisé l'excellent IceDump, le plug-in à tout faire.
Z0mbie, pour ces très bons articles, ces recherches, son effort de traduction et la diffusion de son code.
Frog's Print & Spath, qui ont écrit le programme qui m’a certainement fait gagné le plus de temps.
C-Dilla, qui nous a proposé ici des heures d’amusement.
Illusion Softworks, pour le développement du jeu Mafia que j’ai bien aimé.
ainsi tout les auteurs des proggies que j'utilise.
Et je remercie tout spécialement :
Cork, pour son aide et ses encouragements.
Salutation à toute la communauté francophone des codeurs et à bient?t.
____________________________________________________________________________________________________________________________________
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
A N N E X E S
____________________________________________________________________________________________________________________________________
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
;___________________________________________________________________________________________________________________________________
; Début du code du RemoveSafeDiscPatch v1.0 Part A
;ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
title RemoveSafeDiscPatch v1.0 Part A (Crack & Tutorial by Peex)
.386
.model small, stdcall
option casemap :none
.code
; Ce patch compilé est à charger dans ADump (ou tout segment mémoire libre).
ADump equ 833DE000h ; Adresse du début du segment mémoire créé par ADump
; (à modifier en fonction de son initialisation).
ADumpPatchCode equ (ADump+200h) ; Début du code du patch à 200 (512 octets pour le header du PE).
ADumpPatchData equ (ADump+1000h) ; Début des données à 1000 (utilisé pour stocker la nouvelle table de référencement).
ADumpPatchData2 equ (ADump+2000h) ; Début des données à 2000 (utilisé pour stocker la blacklist).
; Adresses des routines de SafeDisc à modifier (cf. tutorial).
fct2fix1A equ 020ED4B5h ; (Routine de calcul de la référence des Call[.rdata] et des jmp <ref>)
fct2fix1B equ 020ED6feh ; (idem, mais utilisé quand l'adresse à déjà été calculée)
fct2fix2 equ 020AFF37h ; (Routine fixant les Call <ref>)
; Constante dépendant du programme cible a fixer (Valeurs pour le jeu Mafia: Game.exe).
_IB equ 00400000h ; (ImageBase du programme cible)
_text equ 00001000h ; (section faisant partie du programme cible)
_textSize equ 002399BCh ; "
_rdata equ 0023B000h ; "
_rdataSize equ 000B4B7Ch ; "
_stxt774 equ 002FC000h ; "
_stxt774Size equ 0000204Fh ; "
; EntryPoint du RemoveSafeDiscPatch.
start:
pushfd ; On sauvegarde les registres.
pushad ;
call BuildTable ; 1) Calcule la nouvelle table de référencement des appels système.
call CallRefFix ; 2) Corrige les Call[ref] en fonction de la nouvelle table de référencement.
call JmpFix ; 3) Convertit les jmp <ref> en Call[ref] avec la nouvelle table de référencement.
call CallFix ; 4) Corrige les Call <ref> reroutés.
call CopyTable ; 5) Copie la nouvelle table de référencement à la place de l'ancienne (dans .rdata).
popad ; On restore les registres.
popfd ;
int 3 ; Le jeux est ici fixé, le dump est à faire à ce moment (de _IB à _IB+IS).
; Une fois dumpé, mettez l'eip à l'OEP (du jeu) et vérifiez que le jeu fonctionne
; correctement.
call TableCallSkip ; Routine de calcul de la blacklist des call <ref>
; Cette routine est à lancer avant toutes les autres pour construire la liste et le jeu
; doit être redémarré.
int 3 ;
BuildTable proc
; ==> 1 <== Première partie Calcule la nouvelle table de référencement des appels système (stocké dans ADump).
mov eax, _cleanReturnPoint_BuildTable ; Patche la routine (SafeDisc) de détermination de l'appel système original
call PatchMagicSafeDiscFct1 ; et la fait revenir à _cleanReturnPoint_BuildTable.
mov eax, (_IB+_rdata) ; Début de la section .rdata.
mov ebx, ADumpPatchData ; Destination pour la copie des références relogées (dans ADump, à la suite du prog).
_loopBuildTable:
cmp dword ptr[eax], 02BE0000h ; Vérifie si l'appel système a été relogé par SafeDisc (Routines entre 2BE0000 - 2C00000).
jb _copyRef ; Si ce n'est pas un appel à la routine de protection de SafeDisc, on
cmp dword ptr[eax], 02C00000h ; ne touche à rien, car cette adresse est "saine".
jae _copyRef ;
; Arrivé ici, on est s?r d'avoir un appel système relogé par SafeDisc.
jmp dword ptr[eax] ; On cherche donc l'appel système original correspondant en appelant le routine de SafeDisc
; patchée auparavant (c'est en fait la sous-routine qui est patchée). On a alors la référence
; de l'appel système dans ecx lorsque l'on revient à _returnPoint_BuildTable.
_returnPoint_BuildTable:
mov dword ptr[ebx], ecx ; On stocke la référence à l'appel système dans notre table de référencement.
; (dans le partie donnée de Adump: ADumpPatchData)
jmp _continueBuildTable ; Et on continue.
_copyRef:
mov ecx, dword ptr[eax] ; Ce n'est pas un appel relogé, on le copie tel quel.
mov dword ptr[ebx], ecx ;
_continueBuildTable:
add eax, 4 ; On avance d'un dword dans .rdata.
add ebx, 4 ; De même pour notre nouvelle table de référencement dans ADump.
cmp eax, (_IB+_rdata+250h) ; On boucle tant que l'on est pas arrivé a .rdata+250h (Fin des appels relogés)
jne _loopBuildTable ;
ret ; Fin de la routine 2, retour à l'appelant.
; Point de retour le la fonction SafeDisc patchée. Il faut avant tout restaurer les données
; écrasées par le jmp. (co?t de 5 bytes)
_cleanReturnPoint_BuildTable:
mov esp, [ebp+0Ch] ; On replace le code que l'on a enlevé.
popad ; |
popfd ; |
pop ecx ; Originellement: "Ret". La pile contient l'adresse de l'appel système,
; un ret à ce moment lance donc l'appel système. On le remplace par un pop ecx pour
; récupérer cette valeur.
jmp _returnPoint_BuildTable ; On revient a notre patch (Tout est propre).
BuildTable endp
CallRefFix proc
; ==> 2 <== Seconde partie Corrige les références des "Call[ref]" du programme (section .text) en fonction de la
; nouvelle table de référencement des appels système.
mov eax, _cleanReturnPoint_CallRefFix ; Patche la routine (SafeDisc) de détermination de l'appel système original
call PatchMagicSafeDiscFct1 ; et la fait revenir à _cleanReturnPoint_CallRefFix.
mov edx, (_IB+_text) ; Début de la section .text.
_loopCallRefFix:
cmp word ptr[edx], 15FFh ; Est-ce (éventuellement) un "Call [ref]" ?
je _checkCallRef1 ; Si oui, on pousse l'analyse à _checkCallRef1.
_returnCheckCallRef: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopCallRefFix ;
ret ; Fin de la routine 2, retour a l'appelant.
; Première procédure de vérification : Test de validité de zone pour la référence.
_checkCallRef1:
cmp dword ptr[edx+2], (_IB+_rdata) ; Le call pointe sur .rdata à .rdata+250 ? (Zone des routines relogées)
jb _returnCheckCallRef ; Non, alors ce n'est pas un call à un appel système (relogé ou pas).
cmp dword ptr[edx+2], (_IB+_rdata+250h) ;
jae _returnCheckCallRef ;
; Arrivé ici, on a un Call [.rdata à .rdata+250].
; Reste à vérifier si cet appel est relogé par SafeDisc.
; Seconde procédure de vérification : Test de validité de zone pointée.
_checkCallRef2:
mov eax, dword ptr[edx+2] ; Met la valeur du pointeur de .rdata+n dans eax.
; Si eax est compris entre 02BE0000 et 02D00000 c'est un appel à la
; routine de protection de SafeDisc: ~DF394B.
cmp dword ptr[eax], 02BE0000h ;
jb _returnCheckCallRef ; Si ce n'est pas un appel à la routine de protection de SafeDisc, on
cmp dword ptr[eax], 02D00000h ; ne touche a rien, car cet adresse est "saine".
jae _returnCheckCallRef ;
; Arrivé ici, on est s?r d'avoir un appel système relogé par SafeDisc.
; Procédure de détermination de l'appel système original.
_CallRefFix:
; Sauvegarde des registres avant appel inutile, la fonction de SafeDisc est propre.
; On fait croire à la routine de protection qu'elle a été appelée par une fonction
; du jeu. ( pour info: CALL [ref] = push (eip+6), jmp <ref> )
add edx, 6 ; Calcule l'adresse de retour du Call traité.
push edx ; La routine de protection de SafeDisc utilise l'adresse de retour du
; call appelant (dans la pile) pour déterminer l'appel système original.
sub edx, 6 ; On restore edx car on s'en sert comme pointeur dans la boucle principale de scan.
jmp dword ptr[eax] ; On lance l'exécution de la routine SafeDisc pointée qui appelle à son tour la routine
; générale de conversion qui a été patchée auparavant. (Elle va alors jumper sure notre
; patch à _cleanReturnPoint_CallRefFix. On pourra alors récupérer l'appel système original.
_returnPoint_CallRefFix:
; On a à présent la référence de l'appel système original dans ecx pour le Call traité.
pop ebx ; On trash de la pile la valeur de retour du call traité qui servait de fake.
; Reste à modifier l'appel traité en fonction de la nouvelle table.
call GetRefTable ; Calcul la référence de l'appel système pour le nouvelle table de référencement.
mov dword ptr[edx+2], ebx ; Corrige le call traité avec la nouvelle référence calculée (en fonction de la nouvelle
; table). (Rappel: edx = call traité ou encore fonction appelante dans .text)
; Ce call est maintenant fixé pour la nouvelle table (qu'il reste encore à copier)
jmp _returnCheckCallRef ; Continue le patch.
_cleanReturnPoint_CallRefFix:
mov esp,[ebp+0Ch] ; On replace le code que l'on a enlevé
popad ; |
popfd ; |
pop ecx ; Originellement: "Ret". La pile contient l'adresse de l'appel système,
; un ret à ce moment lance donc l'appel système. On le remplace par un pop ecx pour
; récupérer cette valeur.
jmp _returnPoint_CallRefFix ; On revient a notre patch (Tout est propre).
CallRefFix endp
JmpFix proc
; ==> 3 <== Troisième partie Convertit les jmp <.stxt ref> en Call[.rdata] en fonction de la nouvelle table
; de référencement.
mov eax, _cleanReturnPoint_JmpFix ; Patche la routine (SafeDisc) de détermination de l'appel système original
call PatchMagicSafeDiscFct1 ; et la fait revenir à _cleanReturnPoint_JmpFix.
mov edx,(_IB+_text) ; Début de la section .text
_loopJmpFix:
cmp byte ptr[edx],0E9h ; Es-ce un "Jmp ref" ?
je _CheckJmp ; Si oui, on pousse l'analyse à _CheckJmp.
_returnCheckJmp: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx,(_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopJmpFix ;
ret ; Fin de la routine 3, retour a l'appelant.
_CheckJmp:
; Il faut s'assurer que ce jump pointe bien sur la section stxt774 (stxt774 contient la gestion de cette protection avec les jmp)
mov eax, dword ptr[edx+1] ; Calcul de l'adresse de destination du jump traité.
add eax, edx ;
add eax, 5 ;
cmp eax,(_IB+_stxt774) ; On vérifie si ce saut pointe dans la section stxt774.
jb _returnCheckJmp ;
cmp eax,(_IB+_stxt774+_stxt774Size) ;
jae _returnCheckJmp ;
; Ici, on est s?r d'avoir un call[ref .rdata] modifié par SafeDisc en jmp <ref .stxt774>
jmp eax ; On simule le lancement de cette fonction pour obtenir l'appel système original.
_returnPoint_JmpFix:
pop ebx ; On trash de la pile la référence que la routine de SafeDisc avait calculé pour le
; retour du jmp (car la fonction système termine par un ret quant elle a fini).
call GetRefTable ; Calcule la référence à l'appel système pour le nouvelle table (en vue de transformer le
; jmp en Call.
mov word ptr[edx], 15FFh ; On transforme le jmp en Call[ref].
mov dword ptr[edx+2], ebx ; Et on corrige la référence du call[ref] avec la nouvelle référence calculée pour la
; nouvelle table de référencement.
; (Rappel edx = fonction appelante dans .text)
; Ce call est maintenant fixé pour la nouvelle table (qu'il reste encore à copier).
jmp _returnCheckJmp ; Continue le patch.
_cleanReturnPoint_JmpFix:
mov esp,[ebp+0Ch] ; On replace le code que l'on a enlevé
popad ; |
popfd ; |
pop ecx ; Originellement: "Ret". La pile contient l'adresse de l'appel système,
; un ret à ce moment lance donc l'appel système. On le remplace par un pop ecx pour
; récupérer cette valeur.
jmp _returnPoint_JmpFix ; On revient a notre programme
JmpFix endp
CallFix proc
; ==> 4 <== Quatrième partie Reroutage des Call <403389> en call <ref>
mov eax, _cleanReturnPoint_CallFix ; Patche l'autre routine (SafeDisc) de détermination de l'appel original
call PatchMagicSafeDiscFct2 ; et la fait revenir à _cleanReturnPoint_CallFix.
mov edx, (_IB+_text) ; Début de la section .text.
_loopCallFix:
cmp byte ptr[edx],0E8h ; Es-ce un "Call ref" ?
je _CheckCall ; Si oui, on pousse l'analyse à _CheckCall.
_returnCheckCall: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopCallFix ;
ret ; Fin de la routine 3, retour à l'appelant.
_CheckCall:
mov eax, dword ptr[edx+1] ; Calcul de l'adresse de destination du Call traité.
add eax, edx ;
add eax, 5 ; (Note: 5, c'est la nombre d'octets que prend le call lui même: E8 ?? ?? ?? ??)
; ( même chose pour les jmp.)
cmp eax, 00403389h ; Es-ce que c'est un jump à rerouter (403389 = routine de reroutage des appels interne).
jnz _returnCheckCall ;
; Vérifie que cette référence un trap : call à n+5 (appelé call débile dans le tutorial)
call FindCallSkip ; Recherche la référence dans la tables des bans (blacklist dans ADumpData2).
cmp ebx, 1 ; Si on a trouvé cette référence, il faut la nopper au lieux de la convertir.
je _nopCall
; Restauration de l'appel original à la place du call 403389.
; On va simuler le call traité (Call Fake) pour récupérer l'appel original.
add edx, 5 ; On met l'adresse de retour du call dans la pile, pour simuler le point d'appel.
push edx ;
sub edx, 5 ; On restore edx, car on s'en sert pour la progression du scan.
jmp eax ; On jump sur 403389, avec la valeur de retour (fake) dans la pile.
_returnPoint_CallFix:
; On a à présent la référence de la routine à appeler dans ecx, il faut donc recalculer
; le call en fonction de la routine à appeler et de la position de ce call.
pop eax ; Trash la valeur fake de la pile. (eax n'est plus utilisé)
sub ecx, edx ; Calcule la valeur du saut à effectuer à partir de la position du call traité.
sub ecx, 5 ;
mov dword ptr[edx+1], ecx ; Et corrige le call traité avec la valeur du saut à réaliser pour atteindre la
; routine interne originale.
jmp _returnCheckCall ; Continue le patch.
_nopCall:
mov dword ptr[edx], 90909090h ; On noppe le call trap (5 nop)
mov byte ptr[edx+4], 90h ;
jmp _returnCheckCall ; Continue le patch.
_cleanReturnPoint_CallFix:
pop ecx ; On replace le code que l'on a enlevé
pop eax ; |
popad ; |
popfd ; |
pop ecx ; Originellement: "Ret". La pile contient l'adresse originale,
; un ret a ce moment lance donc l'appel. On le remplace par un pop ecx pour
; récupérer cette valeur.
jmp _returnPoint_CallFix ; On revient a notre programme
CallFix endp
CopyTable proc
; ==> 5 <== Cinquième partie Copie de la nouvelle table de référencement des appels systèmes sur l'ancienne (.rdata)
mov eax, (_IB+_rdata) ; Début de la section .rdata.
mov ebx, ADumpPatchData ; Début de la nouvelle table de référence d'appels système calculés.
_loopCopyTable:
mov ecx, dword ptr[ebx] ; On copie les valeurs de la nouvelle table dans .rdata.
mov dword ptr[eax], ecx ;
add eax, 4 ; Avance d'un dword dans .rdata et dans notre table.
add ebx, 4
cmp eax, (_IB+_rdata+250h) ; On boucle tant que l'on n’a pas copié les 250 valeurs.
jne _loopCopyTable ;
ret
CopyTable endp
GetRefTable proc
; ==> Annexe 1 : GetRefTable <==
; Fonction qui cherche une valeur dans notre table de référencement et retourne l'indice par rapport à .rdata.
; Entrée : ecx : Valeur recherchée dans la table (Un appel système).
; Sortie : ebx : Valeur du futur .rdata référen?ant cet appel système.
mov ebx, ADumpPatchData ; Début de la nouvelle table des appels systèmes (stocké dans dans ADump).
_scanMatchRef: ; On cherche la correspondance dans notre table avec l'appel système donné dans ecx.
cmp dword ptr[ebx], ecx ; On compare l'appel système donné à la valeur dans notre table à l'indice ebx.
je _matchRef ; Si ?a correspond, on a trouvé notre indice.
add ebx, 4 ; Sinon, on continue le parcourt de la table un dword plus loin.
cmp ebx, (ADumpPatchData+250h) ; On continue la recherche jusqu'a .rdata+250.
jne _scanMatchRef ;
int 3 ; Pas trouvé ! Y’a un bug... :(
_matchRef:
sub ebx, ADumpPatchData ; On calcule la valeur de décalage.
; ebx contient donc l'indice de la table ou l'on a trouvé la correspondance.
add ebx, (_IB+_rdata) ; On ajoute alors la valeur de base de .rdata à cet indice.
ret ; Retour à l'appelant.
GetRefTable endp
TableCallSkip proc
; ==> Annexe 2: TableCallSkip <==
; Fonction qui construit la table des CALL 403389 à éviter (appelé call trap ou encore call débiles)
mov eax, _cleanReturnPoint_TableCallSkip ; Patche l'autre routine (SafeDisc) de détermination de l'appel original
call PatchMagicSafeDiscFct2 ; et la fait revenir à _cleanReturnPoint_TableCallSkip.
mov ebx, ADumpPatchData2 ; Segment mémoire ou l'on stocke la table des call à ne pas traiter et à nopper.
mov edx, (_IB+_text) ; Début de la section .text.
_loopTableCallSkip:
cmp byte ptr[edx],0E8h ; Es-ce un "Call ref" ?
je _CheckCallSkip ; Si oui, on pousse l'analyse à _CallSkip.
_returnCheckCallSkip: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopTableCallSkip ;
ret ; Fin de la routine 3, retour a l'appelant.
_CheckCallSkip:
mov eax, dword ptr[edx+1] ; Calcul de l'adresse de destination du Call traité.
add eax, edx ;
add eax, 5 ; (Note: 5, c'est la nombre d'octets que prend le call lui même: E8 ?? ?? ?? ??)
; ( même chose pour les jmp.)
cmp eax, 00403389h ; Es-ce que c'est un jump à rerouter (403389 = routine de reroutage des appels internes).
jnz _returnCheckCallSkip ;
; On va simuler le call traité (Call Fake) pour récupérer l'appel original.
add edx, 5 ; On met l'adresse de retour du call dans la pile, pour simuler le point d'appel.
push edx ;
sub edx, 5 ; On restore edx, car on s'en sert pour la progression du scan.
jmp eax ; On jump sur 403389, avec la valeur de retour (fake) dans la pile.
_returnPoint_TableCallSkip:
; On a à présent la référence de la routine à appeler dans ecx, il faut donc recalculer
; le call en fonction de la routine à appeler et le la position de ce call.
pop eax ; La valeur fake posé dans la pile va être réutilisée pour la comparaison à venir.
; Détection des cs:n call cs:n+5 (call trap ou encore call débile).
cmp ecx, eax ; Compare l’adresse de la fonction appelé à l'adresse de retour de la fonction appelante
jne _returnCheckCallSkip ; Si elles sont égales, on a un cs:n call cs:n+5 (call trap)
mov dword ptr[ebx], edx ; C'est un call à éviter, on l'ajoute à notre liste dans ADumpPatchData2
; (pour noppage ultérieur).
add ebx, 4 ; Et on avance d'un dword dans notre liste.
jmp _returnCheckCallSkip ; Continue le patch.
_cleanReturnPoint_TableCallSkip:
pop ecx ; On replace le code que l'on a enlevé
pop eax ; |
popad ; |
popfd ; |
pop ecx ; Originellement: "Ret". La pile contient l'adresse originale,
; un ret a ce moment lance donc l'appel. On le remplace par un pop ecx pour
; récupérer cette valeur.
jmp _returnPoint_TableCallSkip ; On revient a notre programme
TableCallSkip endp
FindCallSkip proc
; ==> Annexe 3 : FindCallSkip <==
; Fonction qui recherche un référence dans la table des bans (blackliste)
; Entrée : edx : Référence à chercher dans la table.
; Sortie : ebx : True/False
mov ebx, ADumpPatchData2 ; Segment mémoire ou l'on stocke la table des call à ne pas traiter.
_loopFindRef: ;
cmp dword ptr[ebx], edx ; Comparaison de le référence recherché avec celle de la table à l’indice ebx.
je _findRef ;
add ebx, 4 ; Sinon, on continue le parcourt de la table un dword plus loin.
cmp ebx, (ADumpPatchData2+400h) ; On continue la recherche jusqu'a 1024 références. (Réajuster si nécessaire)
jne _loopFindRef ;
_notFindRef:
mov ebx, 0 ; Retourne 0 dans ebx si non trouvé.
ret ;
_findRef:
mov ebx, 1 ; Retourne 1 dans ebx si trouvé.
ret ;
FindCallSkip endp
PatchMagicSafeDiscFct1 proc
; ==> Annexe 4.A : Patch d'une routine de SafeDisc : Calcul des appels systèmes pour les Call[.rdata] et jmp<ref stxt> relogés <==
; (Evite les modifications manuelles sous SoftIce)
; Lorsque cette fonction est appelée, eax contient le point de retour souhaité. Mais cette valeur est donnée par rapport au
; patch compilé (son IB) et non par rapport au patch dans ADump. Il faut donc commencer par réajuster cette valeur.
sub eax, 401000h ; On soustrait l'IB du patch.
add eax, ADumpPatchCode ; On ajoute l'adresse de début du code du patch stocké sur ADump.
mov ebx, eax ; On en fait un copie pour une seconde modification.
sub eax, fct2fix1A ; On calcule la valeur du saut à faire a partir de fct2fix1A pour arriver en eax.
sub eax, 5 ; (fct2fix1A = adresse où on doit quitter la routine de SafeDisc pour récupérer la
; valeur de l'appel système original et éviter son lancement.)
mov byte ptr[cs:fct2fix1A], 0E9h ; On ecrit un jmp à fct2fix1A (E9 ?? ?? ?? ??).
mov dword ptr[cs:fct2fix1A+01], eax ; Et on fixe la valeur du saut (précédemment calculé).
sub ebx, fct2fix1B ; Il y a deux points de retour pour cette routine de SafeDisc. La seconde est utilisée
sub ebx, 5 ; quand la valeur de l'appel système a déjà été calculée avant.
mov byte ptr[cs:fct2fix1B], 0E9h ; (Idem, seule l'adresse change)
mov dword ptr[cs:fct2fix1B+01], ebx ;
ret
PatchMagicSafeDiscFct1 endp
PatchMagicSafeDiscFct2 proc
; ==> Annexe 4.B : Patch d'une autre routine de SafeDisc : Calcul des appels système originaux pour les Call 403389 <==
; (Evite des modifications manuelles sous SoftIce)
sub eax, 00401000h ; Idem.
add eax, ADumpPatchCode ;
sub eax, fct2fix2 ;
sub eax, 5 ;
mov byte ptr[cs:fct2fix2], 0E9h ;
mov dword ptr[cs:fct2fix2+01], eax ;
ret
PatchMagicSafeDiscFct2 endp
end start
;___________________________________________________________________________________________________________________________________
; Fin du code du RemoveSafeDiscPatch v1.0 Part A
;ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
;___________________________________________________________________________________________________________________________________
; Début du code du RemoveSafeDiscPatch v1.0 Part B
;ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
title RemoveSafeDiscPatch v1.0 Part B (Crack & Tutorial by Peex)
.386
.model small, stdcall
option casemap :none
.code
; Ce patch compilé est à charger dans ADump (ou tout segment mémoire libre).
ADump equ 83524000h ; Adresse du début du segment mémoire créé par ADump
; (à modifier en fonction de son initialisation).
ADumpPatchCode equ (ADump+200h) ; Début du code du patch à 200 (512 octets pour le header du PE).
; Adresses de la routine de SafeDisc à modifier (cf. tutorial).
fct2fix equ 020BAED7h ; -> Routine de calcul et de réécriture du code modifié
; Constante dépendante du programme cible a fixer (Valeurs pour le jeu Mafia: Game.exe).
_IB equ 00400000h ; (ImageBase du programme cible)
_text equ 00001000h ; (section faisant partie du programme cible)
_textSize equ 002399BCh ; "
; EntryPoint du RemoveSafeDiscPatch.
start:
pushfd ; On sauvegarde les registres.
pushad ;
call ReconstructP1 ; Corrige les modifications à base de 0F 01 C? (SGDT/SIDT).
call ReconstructP2 ; Corrige les modifications à base de 0F AA (RSM).
call ReconstructP3 ; Corrige les modifications à base de 0F 0B (UD2).
call ReconstructP4 ; Corrige les modifications à base de CC (Int 3).
popad ; On restore les registres.
popfd ;
nop ; Le jeu est ici fixé, le dump de la section .text est à faire à ce moment.
; Une fois dumpé, mettez l'eip à l'OEP (du jeux) et vérifiez que le jeux fonctionne
; correctement.
ReconstructP1 proc
mov eax, _cleanReturnPoint_ReconstructP1
call PatchMagicSafeDiscFct ; Patche la routine (SafeDisc) de détermination de l'appel système original
; et la fait revenir à _cleanReturnPoint_ReconstructP1.
mov edx, (_IB+_text) ; Début de la section .text.
_loopReconstructP1:
cmp word ptr[edx], 010Fh ; Es-ce (éventuellement) un "0F 01": SGDT/SIDT ?
je _checkReconstructP1 ; Si oui, on pousse l'analyse à _checkReconstructP1.
_returnCheckReconstructP1: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopReconstructP1 ;
ret ; Fin de la routine, retour a l'appelant.
_checkReconstructP1:
mov eax, dword ptr[edx+2] ; Vérification du 3ème byte: Es-ce un C?
and eax, 0F0h ; On ne s'occupe que de la première partie du byte (xxxx 0000).
cmp eax, 0C0h ; Et on effectue la comparaison pour savoir si cette 1ère partie est un C.
je _continueReconstructP1 ; Si oui, on traite ce cas de code modifié.
jmp _returnCheckReconstructP1 ; Si non, c'est probablement une instruction "normale".
_continueReconstructP1:
; Arrivé ici, on a une cha?ne 0F 01 C?.
jmp edx ; Lance l'exécution de cette instruction, et donc des routines de recalcul du
; code (routines de SafeDisc).
_returnPoint_ReconstructP1: ; On revient ici après avoir modifié le code (routine SafeDisc patchée).
jmp _returnCheckReconstructP1 ; Et on continue le traitement.
_cleanReturnPoint_ReconstructP1:
lea esp, [esp+0Ch] ; On recode les instructions effacées avec notre jump.
jmp _returnPoint_ReconstructP1 ; On revient à notre patch (tout est propre).
ReconstructP1 endp
ReconstructP2 proc
mov eax, _cleanReturnPoint_ReconstructP2
call PatchMagicSafeDiscFct ; Patche la routine (SafeDisc) de détermination de l'appel système original
; et la fait revenir à _cleanReturnPoint_ReconstructP2.
mov edx, (_IB+_text) ; Début de la section .text.
_loopReconstructP2:
cmp word ptr[edx], 0AA0Fh ; Est-ce (éventuellement) un "0F AA": RSM ?
je _continueReconstructP2 ; Si oui, on traite le cas.
_returnCheckReconstructP2: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopReconstructP2 ;
ret ; Fin de la routine, retour a l'appelant.
_continueReconstructP2:
; Arrivé ici, on a une cha?ne 0F AA.
jmp edx ; Lance l'exécution de cette instruction, et donc des routines de recalcul du
; code (routines de SafeDisc).
_returnPoint_ReconstructP2: ; On revient ici après avoir modifié le code (routine SafeDisc patchée).
jmp _returnCheckReconstructP2 ; Et on continue le traitement.
_cleanReturnPoint_ReconstructP2:
lea esp, [esp+0Ch] ; On recode les instructions effacé avec notre jump.
jmp _returnPoint_ReconstructP2 ; On revient à notre patch (Tout est propre).
ReconstructP2 endp
ReconstructP3 proc
mov eax, _cleanReturnPoint_ReconstructP3
call PatchMagicSafeDiscFct ; Patche la routine (SafeDisc) de détermination de l'appel système original
; et la fait revenir à _cleanReturnPoint_ReconstructP3.
mov edx, (_IB+_text) ; Début de la section .text.
_loopReconstructP3:
cmp word ptr[edx], 0B0Fh ; Es-ce (éventuellement) un "0F 0B": UD2 ?
je _continueReconstructP3 ; Si oui, on traite le cas.
_returnCheckReconstructP3: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopReconstructP3 ;
ret ; Fin de la routine, retour a l'appelant.
_continueReconstructP3:
; Arrivé ici, on a une cha?ne 0F 0B.
jmp edx ; Lance l'exécution de cette instruction, et donc des routines de recalcul du
; code (routines de SafeDisc).
_returnPoint_ReconstructP3: ; On revient ici après avoir modifié le code (routine SafeDisc patchée).
jmp _returnCheckReconstructP3 ; Et on continue le traitement.
_cleanReturnPoint_ReconstructP3:
lea esp, [esp+0Ch] ; On recode les instructions effacé avec notre jump.
jmp _returnPoint_ReconstructP3 ; On revient a notre patch (tout est propre).
ReconstructP3 endp
ReconstructP4 proc
mov eax, _cleanReturnPoint_ReconstructP4
call PatchMagicSafeDiscFct ; Patche la routine (SafeDisc) de détermination de l'appel système original
; et la fait revenir à _cleanReturnPoint_ReconstructP4.
mov edx, (_IB+_text) ; Début de la section .text.
_loopReconstructP4:
cmp word ptr[edx], 0CCCCh ; Est-ce (éventuellement) un "CC": INT3 (x2 x3 xN) ?
je _continueReconstructP4 ; Si oui, on traite le cas.
_returnCheckReconstructP4: ; Point de retour des routines ultérieures.
inc edx ; Si non, on continue le scan.
cmp edx, (_IB+_text+_textSize) ; Fin de la section .text ?
jne _loopReconstructP4 ;
ret ; Fin de la routine, retour a l'appelant.
_continueReconstructP4:
; Arrivé ici, on a une cha?ne CC CC.
; On procède au comptage du nombre de CC qui suit le début de la cha?ne.
mov ebx, 0 ; On initialise le compteur ebx
_loopCounter:
cmp byte ptr[edx+ebx+1], 0CCh ; On regarde si on a encore un "CC" ebx après le début.
jne _countFinish ; Si ce n'est pas un CC, le comptage est terminé.
inc ebx ; Sinon, on incrémente le compteur et on continue.
jmp _loopCounter
; ebx contient à présent le nombre de CC après edx.
_countFinish:
cmp byte ptr[edx-1], 0C3h ; Vérifie si ce n'est pas un RET (C3) en edx-1.
je _skipReconstructP4 ; Si c’est le cas, inutile de lancer le traitement, c’est un cas bidon.
; Arrivé ici, on a un cas valide à modifier.
jmp edx ; Lance l'exécution de cette instruction, et donc des routines de recalcul du
; code (routines de SafeDisc).
_returnPoint_ReconstructP4: ; On revient ici après avoir modifié le code (routine SafeDisc patché).
_skipReconstructP4:
add edx,ebx
jmp _returnCheckReconstructP4 ; Et on continue le traitement.
_cleanReturnPoint_ReconstructP4:
lea esp, [esp+0Ch] ; On recode les instructions effacé avec notre jump.
jmp _returnPoint_ReconstructP4 ; On revient a notre patch (Tout est propre).
ReconstructP4 endp
PatchMagicSafeDiscFct proc
; ==> Annexe : Patch d'une routine de SafeDisc <==
; (Evite les modifications manuelles sous SoftIce)
sub eax, 401000h ; cf. RemoveSafeDiscPatch v1.0 Part A
add eax, ADumpPatchCode ;
sub eax, fct2fix ;
sub eax, 5 ;
;
mov byte ptr[cs:fct2fix], 0E9h ;
mov dword ptr[cs:fct2fix+01], eax ;
ret
PatchMagicSafeDiscFct endp
end start
;___________________________________________________________________________________________________________________________________
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课