16/07/2011

Dungeon Siege for the lulz

Introduction

Il y a bien longtemps avec un ami nous jouions à Dungeon Siege, un jeu de role/action développé par Gas Powered Games sorti le 7 mai 2002. Dans cette article je me suis penché sur le fait de me faire un no-cd et etre en mesure de parser leurs fichiers de sauvegarde multijoueur.

No-Cd

Première chose à faire, c'est de regarder si le binaire est packé :

dungeon_peid

Ici nous avons affaire avec un packer dont je ne connaissais pas l'existance, et en googelant, on tombe sur aucun tutorial pour pouvoir l'unpacker mais on va voir quand fait il n'y a rien de compliqué.
Quand on ouvre le binaire sous OllyDbg, on tombe sur la chose suivante :

007C0000 > /EB 10           JMP SHORT DSLOA.007C0012
007C0002   |0300            ADD EAX,DWORD PTR DS:[EAX]
007C0004   |0000            ADD BYTE PTR DS:[EAX],AL
007C0006   |0200            ADD AL,BYTE PTR DS:[EAX]
007C0008   |0000            ADD BYTE PTR DS:[EAX],AL
007C000A   |0000            ADD BYTE PTR DS:[EAX],AL
007C000C   |0000            ADD BYTE PTR DS:[EAX],AL
007C000E   |0000            ADD BYTE PTR DS:[EAX],AL
007C0010   |0000            ADD BYTE PTR DS:[EAX],AL
007C0012   \55              PUSH EBP
007C0013    E8 00000000     CALL DSLOA.007C0018
007C0018    5D              POP EBP
007C0019    81ED 18000000   SUB EBP,18

En voyant la chose suivante, on peut émettre l'hypothèse suivante :
Le packer fait un call à une routine qui vient s'occuper de decompresser l'éxecutable, puis jump ou ret sur le real entry point. Il nous suffit donc de step jusqu'au CALL, placer un hardware breakpoint sur l'acces à la valeur de ESP au moment de cette appel, et nous devrions arriver sur l'entry point.

dungeon_esp

On run le programme, et après s'etre mangé quelques exceptions dans la tronche que l'on repasse au programme, on arrive ici :

0040A39B      55            DB 55                                    ;  CHAR 'U'
0040A39C      8B            DB 8B
0040A39D      EC            DB EC
0040A39E      6A            DB 6A                                    ;  CHAR 'j'
0040A39F      FF            DB FF
0040A3A0      68            DB 68                                    ;  CHAR 'h'
0040A3A1      E8            DB E8
0040A3A2      96            DB 96
0040A3A3      72            DB 72                                    ;  CHAR 'r'
0040A3A4      00            DB 00
0040A3A5      68            DB 68                                    ;  CHAR 'h'

On analyse le code, et on voit la mise en place d'une SEH, un appel à kernel32!GetVersion, GetCommandLineA, ca a l'air de ressembler à notre real OEP. On dump donc notre processus, et on reconstruit l'IAT avec ImportRec :

dungeon_dump
dungeon_imp

En fait à partir de là le no-cd est deja fait, car ces boulets cré un processus qui va etre chargé de vérifier si le cd est bien dans le lecteur, si oui on continue la décompression et on lance le jeu, sinon on ExitProcess.
En fait on bypass cette vérification en unpackant à la main en ayant bien mis le cd original dans le lecteur cd et le tour est joué étant donné que la vérification n'aura plus lieu.

dungeon_process

I want to cheat

J'étais un peu sur ma faim après cette protection lame.
Je me suis donc mis en tete d'écrire un lecteur / unpacker pour les sauvegardes de types multijoueur (fichier .dsparty). Un logiciel fait deja bien le travail "TankViewer", mais malheuresement, source closed c'est moche.
Je décide donc de regarder d'un peu plus près avec un éditeur hexa :
dungeon_winhex1

dungeon_winhex2

La première image ressemble typiquement à un header de sauvegarde pour Dungeon Siege, j'ai meme entouré des offset plutot intéressants qu'on retrouve sur la deuxieme image, ou on peut voir le nom de 3 fichiers :

C'est en googelant des betises que j'ai réussi à tomber sur ce site, qui grace auquel j'ai tout dessuite compris comment était formé les fichiers de sauvegarde.
A partir de là j'étais en mesure de pouvoir ecrire un décodeur fonctionnel.
Il faut savoir que chacun des 3 fichiers est compressé avec zlib. D'ailleurs ca m'a fait pas mal chié de faire marcher cette daube avec MinGW. Je vais pas éterniser la chose et realease les sources : Reader.c, Reader.h.
Une fois les 3 fichiers récupérés, ils sont tres facile à comprendre ansi qu'à parser, voici un exemple.
On peut trés bien imaginer la suite, créer un éditeur complet de sauvegarde ( cheateur ? ), mais lister tous les objets ca relève de beaucoup de travail et je n'y vois pas vraiment d'amusement.
Voici quand meme un apercu de l'output de mon outil :

reader_preview