Je vais essayer de vous expliquer ma démarche, c'est donc normal si c'est un peu fouilli :P !

Avant toute chose il faut arriver à l'oep de notre jeux, c'est extremement simple, en effet SecuROM ne met pas du tout l'accent sur les anti debuggers, encore une fois je n'en ai vu aucun, seul de les DRs sont ecrasé via des exceptions, rien de bien mechant.

Pour arriver a l'oep, il faut ignorer les division par 0 et les memory access violation, ensuite il suffit de mettre un log BP sur WriteProcessMemory qui enregistrera l'addresse de retour, on lance, on regarde quel est le dernier appel à WriteProcessMemory avant que le jeu ne se lance, on redemmare le process et on met un BP conditionnel sur WriteProcessMemory avec la condition "[esp] == DerniereAddresse" ensuite on a plus qu'à tracer à la sortie du call jusqu'à tomber sur un jmp eax qui nous menne à l'oep.

Maintenant que nous sommes à l'oep, nous devons reconstruire les call [API] qui ont été redirigés par SecuROM, c'est là que l'on voit les première difference avec Diablo II.

Il y a toujours des call SecuROM et des call [SecuROM] comme dans D2 mais on ne peut plus mettre un bete JMP vers notre hook, en effet des CRC sont fait sur le code et SecuROM nous envoit balader apres quelques calls, la solution est donc d'utiliser des HBPs qui ne vont pas altérer le code.

On pose donc des HBP grace à une petite exception, on modifie un peu le code du call fixer de D2, on reconstruit tout et ....

(suspens)

Quand on relance l'appli on se rend compte que les call [API] sont faux ! On cogite un peu, on fait 2-3 tests et finalement, surprise, SecuROM pour nous embéter utilise une technique original anti-Call fixers !

En effet si l'on reconstruit "trop vite" les calls, il nous renvoit des addresses bidon !

La solution ici est de mettre un petit sleep avec une valeure aleatoire et pour plus de suretée j'ai aussi decidée de reconstruire les calls de facon aleatoire (et non les uns à la suite des autres, dans l'ordre où je les detecte), je ne sais pas si cette dernière précaution est necessaire mais bon, ca fait plus classe :P .

On refixe donc notre dump avec notre nouveau call fixer et ca marche, on est content.

Le jeux se lance, on peut commencer une partie, l'entrainement se déroule bien, la première mission aussi, les problèmes n'arrivent qu'après avoir été tué quelques fois et apres quelques minutes de jeux, on a alors de gros probleme graphiques.

Les ennemis et les amis se liquéfient et il ne reste que des sac à dos (sac à dos ! sac à dos !) qui zig-zaguent avec des armes attachées dessus, c'est assez marrant mais peut pratique pour repérer les viets dans les broussailles, on passe aussi parfois au travers du decors quand on saute etc. bref, c'est la m****.

Exemple de bug graphique, dans cette image il y a deux vietcongs (morts après 40 balles dans la tête, quand je disais que les triggers influaient aussi sur la jouabilitée ...) sauras tu retrouver le cu-cul et la tê-tête ?

A partir de ce momment là j'ai beaucoup bloqué, en effet les triggers sont des animaux assez farouches ...

Pour les attraper il faut se lever tôt ou savoir où ils se cachent.

Si le devellopeur n'est pas trop c** il va s'arranger pour que les effet de la détection du dump soit silencieux, donc pas de grosse MessageBox "FAIL" facile à trouver et qui permet de remonter le code, juste des effets pernicieux qui vont s'attaquer aux graphismes ou a la jouabilitée.

J'ai d'abord pensé que SecuROM remplaçait la valeure de retour de l'API, j'ai donc modifié mon call fixer pour detecter ça mais ca ne donne aucun resultat, aucune addresse de retour n'est modifié lorsque je fix les calls.

Je pense donc que SecuROM doit hooker les APIs en memoire avec un bon gros JMP hook au debut de l'API, je devellope un plugin pour olly qui detecte les hooks en memoire sur les API, je scann et ... rien

Je commence à être assez desesperé et dans un élan de courage, je décide de poser un BP sur toutes les APIs et de rechercher celles qui semblent "suspects" et après pas mal de F9 et de F2 (pour enlever les BPs sur les APIs non suspects :P) je tombe sur un GetTickCount dont la valeure de retour est utilisée dans des tests, plus que bizard étant donné que GetTickCount renvoie un valeure imprevisible.

Bingo ! j'ai mon premier trigger ! A ce momment là tout s'enchaine.

En tracant dans le call [SecuROM] je me rends compte que l'addresse de retour de l'API est bel et bien modifiée mais qu'à certaines conditions ! C'est pour ça que je ne detectait rien.

La fonction qui sert a redirigé les call [API] fonctionne comme ceci:

  1. Elle cherche l'addresse de l'API redirigée (par exemple GetVersion)
  2. Elle regarde si cette API est susceptible d'être un trigger (GetVersion n'a par exemple pas été choisis par le devellopeur pour mettre en place les triggers mais GetTickCount si)
  3. Si l'API est un trigger en puissance, alors SecuROM va faire une série de test sur les valeures des registres et les arguments passés à l'API, si ces test sont concluants, il renvoie le n° du trigger.
  4. A partir du n° renvoyé, la valeure de retour est remplacée par l'addresse de la routine qui va se charger de modifier la valeure de retour de l'API et la valeure pré-definie que doit renvoyer le trigger est stockée
  5. L'API est appelée
  6. SecuROM remplace la valeure de retour par la valeure pré-définie et rend la main au programme
  7. Le devellopeur fait ce qu'il veut de cette valeure de retour :D

Ce qui est très fun avec les triggers c'est qu'un même call peut être hooké comme ne pas être hooké suivant les arguments qu'on lui passe, de plus l'API est appellée même si c'est un trigger on ne peut donc pas simplement patché le call avec un "mov eax , valeure" comme je le pensais au debut ...

Il faut donc ruser et remplacer SecuROM, voici comment j'ai procédé : Il faut d'abord repérer les call qui sont potentiellement des triggers, heureusement comme je l'ai dit dans la partie d'avant, SecuROM fait ca pour nous, il suffit de mettre un HBP à l'etape 3 pour détecter quelles APIs sont susceptibles d'être des triggers il suffit alors de stocker les addresses au fur et à mesure que l'on fixe les calls.

Ensuite il faut remplacer SecuROM et, comme lui, vérifier les arguments et les valeures des registres pour savoir si l'on doit renvoyer une valeure qu'il va aussi falloir trouver.

Pour faire ca, j'ai analyser avec IDA les routines que SecuROM utilise et j'ai retrouvé leur prototype :

 int __cdecl IsTriggerCall(int ptrNumTrigger, int _EAX, int _EBX, int _ECX, int _EDX, int _ESI, int _EDI, int _ESPm4, int _Arg1, int _Arg2, int _Arg3, int _Arg4, int _Arg5, int RetVal1, int RetVal2, int RetVal3)
 int __cdecl SetVals(int NumTrigger, int _EAX, int _EBX, int _ECX, int _EDX, int _ESI, int _ESPm4, int _Arg1, int _Arg2, int _Arg3, int _Arg4, int _Arg5, int RoutineTrigger, int RetVal1, int RetVal2, int RetVal3, int TriggerRetnVal)

IsTriggerCall renverra TRUE si le call est bien un trigger et nous donnera le n° du trigger, SetVals va se charger de remplacer l'addresse de retour de l'API et va rechercher la valeure pré-définie à renvoyer à partir du n° du trigger.

Ensuite pour chaque trigger potentiel (par exemple tout les call [GetTickCount]) je remplace le call [API] par un call MaRoutine avec MaRoutine une fonction qui va appeller IsTriggerCall et SetVals puis sauter sur l'API.

Tout ceci est fait automatiquement par mon call fixer à condition de lui donner les addresses de IsTriggerCall et SetVals.

Enfin, il y a un dernier check qui est fait par log.dll qui va vérifier que les triggers n'ont pas été patché grâce à un petit CRC, ce dernier check est, je pense, une idée du devellopeur et n'a rien a voir avec SecuROM.

Vous trouverez ICI mon call fixer ainsi que les sources, le call fixer est injecté dans le processus grace à mon plugin IinjOlly qui peut être recupéré ICI (j'ai ajoutée 2 petites options par rapport à celui disponible dans le post de D2).

J'espere que ce post vous aura appris quelques petites choses et qu'il vous aura donné envie de vous frotter au protection de jeux, c'est vraiment quelque chose de simpa !

Enfin si j'ai un conseil à donner aux devellopeurs c'est de ne pas lésiner sur les hidden checks en utilisant à fond le SDK de la protection et en n'hésitant pas à en faire des maisons qui sont bien plus embétant à détecter.