Reverse de Xnview
par
Pass Partout
Avril 2000



Sommaire:

I/ introduction:
a/ commentaires:
b/ matériels:
II/ Structure/ project:
1/ demarrage verfication enregistrer/ non enregistrer:
2/ Suppression de l'option 'Sauve sous' si on n'est pas enregistrer:
3/ Menu d'enregistrement:
III/Programmation:
1/ demarrage verfication enregistrer/ non enregistrer:
2/ Suppression de l'option 'Sauve sous' si on n'est pas enregistrer:
3/ Menu d'enregistrement:
IV/Astuces:
V/ Conclusion:
 

I/ Introduction:

a/ commentaires:

Bonjours,

Xnview est un freeware (très bien fait), l'idée est d'en faire un shareware. Pas si facile que ça: en effet ce n'est plus du cracking mais du reverse et vous le savez tous aussi bien que moi que ce dernier demande beaucoup plus de connaissances.
Je n'ai pas cherché à faire une protection infaillible: loin de là. J'ai cherché au contraire à rendre ma protection la plus comprehensible possible et en vous laissant des espaces pour que vous puissiez la retravailler. J'ai fait le squelette et à vous plus tard d'améliorer cette protection comme vous le voudrez.

Tchao.

b/ matériels:

- Xnview de Pierre Gouelet
- Un editeur hex: hview
- Soft-ice
- Un editeur de ressource
- Prodump 16.2
- W32dasm

II/ Structure/ project:

Mon project se compose en trois partis:
- au demarrage verification enregistrer/ non enregistrer.
- supression de l'option 'Sauver sous'
- menu pour l'enregistrement

1/ demarrage verfication enregistrer/ non enregistrer:

algorithme:

lire code dans un fichier: pp.ini
si vide alors aller à !fin
lire nom dans un fichier: pp.ini
si vide alors aller à !fin
verification du code selon le nom
si code=ok alors demarrer le programme sinon aller a !fin
:fin
ecrire message: 'Enregistrez-vous'
demarrer le programme

2/ Suppression de l'option 'Sauve sous' si on n'est pas enregistrer:

algorithme:

Si 'Sauver sous' est selectioné alors
    si on est enregistré alors sauver le fichier sinon ecrire message:'Version shareware'
    retour programme

3/ Menu d'enregistrement:

algorithme:

Si 'A propos' est selectioné alors
    affiche la boite d'enregistrement
    si bouton 'Enregistrer' est appuyé alors
        ecrire dans le fichier pp.ini le code et le nom
        verification du code selon le nom
        si bon code alors message:'bon code' sinon message:'mauvais code'
    retour au programme

III/ Programmation:

La boite d'enregistrement n'etant pas crée il faut la faire. J'ai grâce à un éditeur de ressource (ressource harcker) pu mettre deux champs pour rentrer le nom et le code avec un bouton d'enregistrement. En gros j'ai edité les ressources, modifié et compilé. Il est conseillé de faire cette étape en premier. Moi meme je l'ai fait plus tard mais c'est une erreur. Cette opération peut-être fait manuellement avec un éditeur hex.
Champ nom, Champ code et le bouton Enregistrer ont pour ID respectivement: 1002(3eah), 1003(3ebh), 1(1h).

Avant de modifier Xnview avec un éditeur hex, il faut déjà trouver de la place. J'ai donc pris Procdump et j'ai regardé les sections (PE Editor/sections) mais je n'ai pas vu beaucoup de places. Pour confirmer j'ai pris un éditeur hex et j'ai listé (en gros) le programme et apparemment je n'ai rien vu. Où trouver de la place? J'ai mis longtemps avant de trouver. La solution est de créer une nouvelle section à la fin du proggramme avec Procdump (PE Editor/ sections/ clic droite sur la derniere section/ add section: nommons la '.xnview') et ensuite d'inserer le nombre d'octect correspondant avec un éditeur hex. J'ai rajoute 1000h octects. Il est important aussi de changer les characteristics de la section '.xnview': C0000040 devient E0000020 (on peut lire, écrire et executer).
Cette opération peut-être fait manuellement avec un éditeur hex.
La nouvelle section commence en 510000:@109a00.

Remarque 1: il est conseillé de prendre Hview pour 2 raisons
a/ l'option decode est trés pratique
b/ hview calcule les sauts pour vous

Remarque 2: il est conseillé d'avoir l'aide win32.hlp sur les APIs.

Remarque 3:
DWORD GetPrivateProfileString(

    LPCTSTR  lpAppName, // points to section name
    LPCTSTR  lpKeyName, // points to key name
    LPCTSTR  lpDefault, // points to default string
    LPTSTR  lpReturnedString, // points to destination buffer
    DWORD  nSize, // size of destination buffer
    LPCTSTR  lpFileName  // points to initialization filename
   );
Pour utiliser cet API, on ecrit en ASM:
68600f5100              push 00510f60 // pp.ini // points to initialization filename
680e000000              push 0000000e // taille maximal // size of destination buffer
68100f5100              push 00510f10 // destination buffet (dans notre section)
6800000000              push 00000000 // valeur de retour si la cle n'est pas trouvé
68500f5100              push 00510f50 // lire CODE (key)
68700f5100              push 00510f70 // lire [xnviewfr] (section)
2EFF15DCF84B00          Call dword ptr cs:[004BF8DC] //GetPrivateProfilestringA
Ce qui semble être l'inverse mais en realite c'est bon car l'empilement fait que le premier arrivé et le dernier à être dépilé. Vous avez compris? Ah, bon.

Remarque 4: je n'ai pas mis les adresses à chaque instruction mais seulement au début d'un call ou d'un endroit que je trouve important.

Remarque 5: il faut avec votre éditeur hex écrire:
NOM--------------> (510f40:@10a940)
CODE-------------> (510f50:@10a950)
pp.ini-----------> (510f60:@10a960)
xnview-----------> (510f70:@10a970)
Enregistrement---> (510f80:@10a980)
Mauvais code-----> (510f90:@10a990)
Bon code---------> (510fa0:@10a9a0)
Shareware--------> (510fb0:@10a9b0)
Ceci est un ...--> (510fc0:@10a9c0)

Remarque 6: Pour avoir l'adresse des APIs utilisées j'ai simplement désassemblé Xnview.exe et regardé dans les importations. Et j'ai trouvé tout ce qu'il me fallait. Dans le cas contraire il aurait fallu une routine de Virogen: Christal vous explira mieux que moi.

1/ demarrage verfication enregistrer/ non enregistrer:

Une fois que cette portion de code a été ecrit on peut changer l'entry point: mettre 110000.

(510000:@109a00) // debut de la section
68600f5100              push 00510f60 // pp.ini
680e000000              push 0000000e // taille maximal
68100f5100              push 00510f10 // destination buffet (dans notre section)
6800000000              push 00000000 // valeur de retour si la clé n'est pas trouvé
68500f5100              push 00510f50 // lire CODE (key)
68700f5100              push 00510f70 // lire [xnviewfr] (section)

DWORD GetPrivateProfileString(

    LPCTSTR  lpAppName, // points to section name
    LPCTSTR  lpKeyName, // points to key name
    LPCTSTR  lpDefault, // points to default string
    LPTSTR  lpReturnedString, // points to destination buffer
    DWORD  nSize, // size of destination buffer
    LPCTSTR  lpFileName  // points to initialization filename
   );

2EFF15DCF84B00          Call dword ptr cs:[004BF8DC] // GetPrivateProfilestringA
66803D010F510000        cmp byte ptr [00510F10], 00 // le code est-il vide?
0f84b60c0000            je 00510d00 // message shareware et saut vers l'entry point 438214
68600f5100              push 00510f60 // pp.ini
680e000000              push 0000000e // taille maximal
68000f5100              push 00510f00 // destination buffet
6800000000              push 00000000 // valeur de retour si la clé n'est pas trouvé
68400f5100              push 00510f40 // lire NOM (key)
68700f5100              push 00510f70 // lire [xnviewfr] (section)
2EFF15DCF84B00          Call dword ptr cs:[004BF8DC] // GetPrivateProfilestringA
66803D000F510000        cmp byte ptr [00510F00], 00 // le nom est-il vide?
0f84b60c0000            je 00510d00 // message shareware et saut vers l'entry point 438214
e8b1080000              call 00510900 // verif du code //valeur de sorti eax 2/3(2:faux/3juste)
83f802                  cmp eax,2 // enregistrer ou non enregistrer
0f84a80c0000            je 00510d00 // message shareware et saut vers l'entry point 438214
e8a30a0000              call 510b00 // ok
e9b281f2ff              jmp 438214 // saut vers l'entry point 438214

(510d00:@10a700) // message shareware et saut vers l'entry point 438214
E8FB000000              call 00510E00 // message shareware
E90a75f2ff              jmp 438214

(510900:@10a300) // !!!verification du code!!! ecx=code//eax=nom

Ici il faudrait faire mieux que ça: faire un générateur de clé.

66803D000F510000        cmp byte ptr [00510F00], 00
0f840c000000            je !!!fin
66803D000F510000        cmp byte ptr [00510F00], 32 -->1
0f850c000000            jne !!!fin
66803D010F510000        cmp byte ptr [00510F01], 33 -->2
0f850c000000            jne !!!fin
b803000000              mov eax,3
c3                      ret
:fin // code faux
b802000000              mov eax,2
c3                      ret

(510b00:@10a500) // !!!OK!!!
66c605f00f510003        mov byte ptr[00510ff0],03 // flag utilisateur en enregistrer ou pas
c3                      ret

(510e00:@10a800) // Call shareware message
6a00                    push 00000000
68b00f5100              push 00510fb0
68c00f5100              push 00510fc0
6a00                    push 00000000
2EFF1584F74B00          Call dword ptr cs:[004BF784] // message box (voir tut de Morgatte)
c3                      ret

2/ Suppression de l'option 'Sauve sous' si on n'est pas enregistrer:

Pour savoir où le programme sauve notre image il suffit de poser un GetSaveFileNamaA. On arrive en 004127D9.
(4127D9@11dd9) // Sauver sous
:004127D9 E827e40f00 call 510c00 // on detourne vers notre patch

Ici on peut faire avec d'autres options.

(510c00:@10a600) // option desactivées:
60                            pushad // sauve les registres
66803DF00F510001              cmp byte ptr [00510FF0], 03 // flag utilisateur
7507                          jne !!!fin
61                            popad // restitue les registres
:00510c0c E8ab81f0ff          call 00418DBC // traitement classique
c3                            ret
:fin
:00510c12 E8e9010000       call 00510e00 // message box shareware
61                            popad // restitue les registres
c3                            ret

3/ Menu d'enregistrement:

int DialogBoxParam(

    HINSTANCE  hInstance, // handle of application instance
    LPCTSTR  lpTemplateName, // identifies dialog box template
    HWND  hWndParent, // handle of owner window
    DLGPROC  lpDialogFunc, // address of dialog box procedure
    LPARAM  dwInitParam  // initialization value
   );

:004010FA 6A00                    push 00000000
:004010FC 68D8104000              push 004010D8 // pointe vers la procedure
:00401101 50                      push eax
:00401102 687C104C00              push 004C107C
:00401107 6A00                    push 00000000
:00401109 2EFF15D0F84B00          Call dword ptr cs:[004BF8D0] // KERNEL32.GetModuleHandleA
:00401110 50                      push eax
:00401111 2EFF159CF64B00          Call dword ptr cs:[004BF69C] // USER32.DialogBoxParamA

(4010FC:@6fc)
6800065100          PUSH    00510600 // vers ma nouvelle procedure

(00510600:@10a000) // notre procedure
55                  PUSH    EBP
8BEC                MOV     EBP,ESP
817D0C11010000      CMP     DWORD PTR [EBP+0C],00000111 // WM_COMMAND: souris, clavier
0f84f0000000       JZ      00510700  // oui -> traitement de l'événement
837D0C10            CMP     DWORD PTR [EBP+0C],10 // fermeture
750C               JNZ     !!!partir // non -> boucle
:fermer dialogboxparama // (510616:@)
6A00                PUSH    00 // valeur de retour
FF7508              PUSH    DWORD PTR [EBP+08] // handle

BOOL EndDialog(

    HWND  hDlg, // handle of dialog box
    int  nResult  // value to return
   );

2EFF15B8F64B00      CALL    CS:[USER32!EndDialog]
:partir // (510622:@)
C9                  LEAVE
C21000              RET     0010

(510700:@10a100) // traitement de l'evenement
8B4510              MOV     EAX,[EBP+10] // message dans EAX
663d0100            cmp ax, 0001 // bouton enregistrement?
0f851bffffff       jnz !!!partir // non -> boucle
6A0e                PUSH    14 // nombre de caractère maxi
68000f5100          push    00510f00 // buffet de stockage
68ea030000          PUSH    000003ea // ID 3ea=champ nom
FF7508              PUSH    DWORD PTR [EBP+08] // handle box

UINT GetDlgItemText(

    HWND  hDlg, // handle of dialog box
    int  nIDDlgItem, // identifier of control
    LPTSTR  lpString, // address of buffer for text
    int  nMaxCount  // maximum size of string
   );

2EFF15F4F64B00      CALL    CS:[USER32!GetDlgItemTextA]
6A0e                PUSH    14 // nombre de caractère maxi
68100f5100          push    00510f10 // buffet de stockage
68eb030000          PUSH    000003eb // ID 3eb=champ code
FF7508              PUSH    DWORD PTR [EBP+08] // handle box
2EFF15F4F64B00      CALL    CS:[USER32!GetDlgItemTextA]
e8c4fcffff         call 00510400 // ecriture dans le fichier pp.ini
60                  pushad // sauve les registres
e8b2010000         call    00510900 // verif du code
83f802              cmp     eax,2 // eax=2: mauvais, eax=3:bon
750a                jne !!!bon code
e8a6fdffff         call    00510500 // messagebox faux code
e90a000000          jmp !!!fin
:bon code
e89cfbffff       call    00510300 // messagebox bon code
e897030000       call    00510b00 // ok
:fin
61                  popad // restitue les registres
e9a7feffff       jmp !!!fermer dialogboxparama

(510400:@109e00) // ecrire dans le fichier pp.ini
68600f5100              push 00510f60 // pp.ini
68100f5100              push 00510f10 // code entree
68500f5100              push 00510f50 // CODE (key)
68700f5100              push 00510f70 // [xnviewfr]

BOOL WritePrivateProfileString(

    LPCTSTR  lpszSection, // address of section name
    LPCTSTR  lpszKey, // address of key name
    LPCTSTR  lpszString, // address of string to add
    LPCTSTR  lpszFile  // address of initialization filename
   );

2EFF15A0F94B00          Call dword ptr cs:[004BF9A0] // WritePrivateStringA
68600f5100              push 00510f60 // pp.ini
68000f5100              push 00510f00 // nom entre
68400f5100              push 00510f40 // NOM (key)
68700f5100              push 00510f70 // [xnviewfr] (section)
2EFF15A0F94B00          Call dword ptr cs:[004BF9A0] // WritePrivateStringA
c3                      ret

(510300:@109d00) // call message box bon code!!!
6a00                    push 00000000
68800f5100              push 00510f80
68a00f5100              push 00510fa0
6a00                    push 00000000
2EFF1584F74B00          Call dword ptr cs:[004BF784] // MessageBox
c3                      ret

(510500:@109f00) // Call enregistrement message faux code
6a00                    push 00000000
68800f5100              push 00510f80
68900f5100              push 00510f90
6a00                    push 00000000
2EFF1584F74B00          Call dword ptr cs:[004BF784] // MessageBox
c3                      ret

Il reste encore à faire disparaitre le menu d'enregistrement une fois que c'est oki. A vous de jouer.

IV/ Astuces:

Remarque: J'ai fait un text readme.txt pour s'enregistrer. C'est un shareware non? Il faut pouvoir s'enregistrer: je sais c'est un détail mais qui peut bien faire la différence :o).

- Vous pouvez maintenant mettre des protections supplementaires: anti...
- Pour être plus efficace au lieu de faire un jmp 00510050 faites un mov eax, 00510050 suivi d'un jmp eax (idem pour un call). Ainsi il n'y aura pas de réference pour ce call.
- Il est préferable d'écrire dynamiquement les chaines de caracteres que de l'écrire avec un éditeur hex. Une recherche avec un éditeur hex sur une chaine de caractère est trop simple à faire et à trouver.
- Mettre le moins d'indices possible: genre des messagebox partout.

V/ Conclusion:

A ce stade cette protection est une véritable passoire. Mais nous avons fait le principal. Il serait interressant de continuer mon travail inachevé.

A bientot

Pass partout
Email: pass_partout@hotmail.com
Site: http://mainrouge.cjb.net
Email groupe: la_main_rouge@caramail.com