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:
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.
- Xnview de Pierre
Gouelet
- Un editeur hex: hview
- Soft-ice
- Un editeur de ressource
- Prodump 16.2
- W32dasm
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
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
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
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.
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.
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