首页
社区
课程
招聘
[求助]请求翻译,好像是法文?有一些是英文?
发表于: 2005-5-6 18:47 7635

[求助]请求翻译,好像是法文?有一些是英文?

2005-5-6 18:47
7635
ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
                                                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直播授课

收藏
免费 0
支持
分享
最新回复 (12)
雪    币: 622
活跃值: (294)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
2
等待高手翻译此篇教学。
2005-5-7 08:43
0
雪    币: 212
活跃值: (105)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
我以前读过这篇文章,很不错的,非常值得一读,算是经典的一篇.
是法文,不过实在太长了,40多页,谁也不没有那么多时间去翻译这么长的文章,建议用一个翻译软件把它翻译为英文,这样读起来比较容易点.
2005-5-9 01:16
0
雪    币: 427
活跃值: (412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
最初由 声声慢 发布
我以前读过这篇文章,很不错的,非常值得一读,算是经典的一篇.
是法文,不过实在太长了,40多页,谁也不没有那么多时间去翻译这么长的文章,建议用一个翻译软件把它翻译为英文,这样读起来比较容易点.


建议你安装一个语音输入软件,这样边念就边翻译了。呵呵
2005-5-9 12:40
0
雪    币: 212
活跃值: (105)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
5
最初由 鸡蛋壳 发布


建议你安装一个语音输入软件,这样边念就边翻译了。呵呵


真的有这样的软件?能不能给介绍几个简单实用的...这样就能一边洗碗,一边写小说了...
2005-5-9 13:59
0
雪    币: 427
活跃值: (412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
最初由 声声慢 发布


真的有这样的软件?能不能给介绍几个简单实用的...这样就能一边洗碗,一边写小说了...


不是吧,纵横网路几年,这个软件曾经火爆一时,国产,国外得都有。国外得是IBM出得,相当得老牌了,国内得就很多了。以前用过国外得,还可以得,用得越久正确率越高。普通话不标准也没关系得。
2005-5-10 14:25
0
雪    币: 212
活跃值: (105)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
7
既然是有的,可以给出它的名称或网站吗? 我在GOOGLE上没找到...
我要的是英文界面的,当我发音的时候,它能自动转化为汉字写下来的...
谢谢了.
2005-5-12 00:50
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
声声要拿这东西写小说?
adm一下先
P.S.
你怎么搞了个贵宾啊
恭喜哦
2005-5-13 20:58
0
雪    币: 212
活跃值: (105)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
9
最初由 xuanqing 发布
声声要拿这东西写小说?
adm一下先
P.S.
你怎么搞了个贵宾啊
恭喜哦


hehe,是啊!最近钱不够用,所以想写点东西挣点钱,不过问了一下环球地理,稿费还真低...50块/1000字...哎

当贵宾还真的开心,从来没有人把我当贵宾..是第一次...要谢谢看雪的绅士风度
2005-5-14 00:06
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
呵呵
恭喜恭喜先
有没有红包拿啊?hoho
我五一也在为别人码字挣点饭钱
不过你的那个也太低了吧
:(
2005-5-19 00:15
0
雪    币: 165
活跃值: (33)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
这法语好难懂
2005-6-21 18:14
0
雪    币: 122
活跃值: (45)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
其实能看懂代码下的东西就都明白了,不管什么语言,程序的代码都是一样的
2005-10-4 14:52
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
晕,都是法语啊,不过同意楼上的
2005-10-16 19:35
0
游客
登录 | 注册 方可回帖
返回
//