|
Reverse Enginering de PrivateEXE v2.0a
Par Nody
Intro.
Logiciel : Tout logiciel protégé par PrivateEXE v2.0a (ou autre version ?)
Source : défi du site http://crackfr.cjb.net/ (groupe
#crack.fr)
Outils : Tasm 5.0, ProcDump v1.x et une petite goutte de Soft-Ice
Protection : x
Niveau : Moyen
Commentaires :
Ce cours nécessite d'avoir quelques connaissances en PE.
C'est parti !
Le logiciel PrivateEXE (que j'appellerais Pexe dans la suite ) propose de protéger les exécutables
par un mot de passe que vous pouvez choisir, bref ça aurait pu être un logiciel intéressant
...
Pour voir à quoi sert Pexe, il faut protéger un exe (je vous en apprends une bonne là ..
:-) ). J'ai choisi winipcfg.exe qui est fourni avec win98 (win95 ?) et je l'ai protégé avec un mot
de passe.
On va d'abord regarder avec ProcDump ce qu'il se passe. Pour cela, démarrer la commande "PE Editor"
et ouvrez la version protégée de winipcfg. Chez moi, j'ai ça :
EntryPoint : 0000D00
Size of image : 0000E000
Image Base : 00400000
et dans "Sections" :
.text
.data
.idata
.rsrc
.007
Pour la version normale de winipcfg, ça donne :
EntryPoint : 000061E0
Size of image : 0000D000
Image Base : 00400000
et dans "Sections" :
.text
.data
.idata
.rsrc
J'ai protégé d'autres programmes, j'ai comparé et ils ont tous comme points communs d'avoir
la section .007 en plus et d'avoir 0x1000h en plus à la taille de l'image.
Maintenant, on va démarrer le winipcfg protégé avec le Symbol Loader de Soft-Ice et on
arrive là :
: 0040D000 push ebx
: 0040D001 call 0040D006
: 0040D006 pop ebx
: 0040D007 mov eax, ebx
: 0040D009 sub eax, 00006E26
: 0040D00E push eax
: 0040D00F etc ....
Voilà à quoi servent ces quelques lignes :
push ebx => place ebx sur la pile
call 40D006 => place 0040D006 sur la pile !!
pop ebx => ebx = 0040D006 = l'offset courant du programme !
Mainteant que le programme connait la position courante, il va faire ça :
mov eax, ebx => eax = 0040D006
sub eax, 6E26 => eax = 004061E0
Ça ne vous rappelle rien 004061E0 ? Additionnez l' ImageBase (0x00400000) et l'EntryPoint (0x000061E0)
du programme originale et ça nous donne 004061E0 !
Donc à ce moment là, le programme s'est un peu trahis car il nous a donné l'EntryPoint
originale !
C'est à dire, que vous pouvez "cracké" cette protection en remplaçant "push
eax" par "jmp eax" !
On va pas s'arreter là, heureusement, on va écrire un programme qui transforme la version protégée
en version *originale". Pour ça, il faut :
-> modifier l'Entry Point (retouver l'EntryPoint original)
-> restaurer l'ImageSize
-> virer la section .007 qui se trouve TOUJOURS à la fin du programme
-> restaurer le nombre de sections
Petite parenthèse sur les PE header :
Le PE header commence à l'adresse donnée en 0x3Ch. Pour winipcfg, le PE header commence en 0x80h.
Les 2 premiers octets sont toujours 'PE'.
Disons que le PE header commence à l'adresse x, alors l'EntryPoint est situé à l'adresse
x + 28h et il est codé sur 4 octets. Pour le winipcfg normal, c'est 0x000061E0 comme nous l'a indiqué
ProcDump. L'EntryPoint sert à savoir où se trouve le début du code.
Puis à l'adresse x + 34h, nous avons l'ImageBase (sur 4 octets aussi) qui sert à savoir à
quelle offset le programme sera chargé. Ici, nous avons 0x00400000. En additionnant l'ImageBase avec l'EntryPoint,
on obtient l'adresse de début du code.
A l'adresse x + 50h, nous avons l'ImageSize (toujours sur 4 octets) qui sert à connaitre le nombre d'octets
que le programme occupera une fois chargé.
Le PE header est long de 0xF8 octets, et après il y a le Section Header qui contient des renseignements
sur les sections, comme le RAW Size (taille de la section une fois chargée), la taille de la section dans
le fichier, et l'adresse de début de la section.
Le plus chiant, c'est de retrouver l'EntryPoint originale, quoique le programme nous le donne quasiment. Pour
retrouver l'EP originale, il faut faire ça :
-> sauvegarder l'EntryPoint du prog protégé
-> lui additionner l'Image Base pour savoir où commence le code
-> choper le dword situé en [EntryPoint + ImageBase + 10] qui contient un nombre important, celui du
sub eax, 6E26
sub eax, 00006E26 en hexa c'est 2C 00 00 6E 26, et bien [EntryPoint + ImageBase + 10] nous fait pointer sur
00 00 6E 26.
-> soustraire le nombre précédement trouvé de l'adresse [EntryPoint + ImageBase + 6]
car quand on fait sub eax, 6E26, à ce moment-là, eax = [EntryPoint + ImageBase + 6]
Une fois qu'on a l'EntryPoint original, on pourra remplacer l'EntryPoint du prog protégé par celle-là.
Ensuite, il ne restera plus qu' à soustraire 0x1000h à l'ImageSize, décrémenter le
nombre de sections car nous allons supprimer la section .007 qui contient la protection, et enfin écrire
tout ça dans un EXE tout propre en faisant attention d'écrire seulement ce nombre d'octets : (Taille_Du_Fichier_Protége
- Taille_De_La_Section_.007).
Source ASM
Voilà la source ASM de mon programme qui nettoie les programmes protéges par PrivateEXE v 2.0a.
Comme je n'ai jamais testé d'autres versions de PrivateEXE, je vous conseille de faire une sauvegarde des
fichiers que vous vouler nettoyer, s' ils ont été protégé par une autre version.
; unPrivate v0.01 par Nody <nodless@usa.net>
; assembler comme ça :
; tasm32 -ml -m5 -q unpriv
; tlink32 -Tpe -aa -x -c unpriv ,,, import32
.386p
.model flat, stdCALL
; liste des API utilisées :
EXTRN ExitProcess:PROC
EXTRN MessageBoxA:PROC
extrn CreateFileA:proc
extrn CloseHandle:proc
extrn ReadFile:proc
extrn WriteFile:proc
extrn SetFilePointer:proc
extrn SetEndOfFile:proc
extrn CreateFileMappingA:proc
extrn MapViewOfFile:proc
extrn UnmapViewOfFile:proc
extrn GetFileSize:proc
extrn SetEndOfFile:proc
extrn GetOpenFileNameA:proc
.DATA
; VARIABLES ET CONSTANTES
Fhandle dd ?
GENERIC_READ equ 80000000h
GENERIC_WRITE equ 40000000h
EntryPoint dd 0
ImageSize dd 0
NbBytesRead dd 0
NbBytesWritten dd 0
Hmem dd ?
addr_file dd ?
FileSize dd 0
ImageBase dd 0
RVA_EP dd 0
Buffer db 40 dup (0)
RAW dd 0
Section dd 0
SectionSize dd 0
SizeofRawData dd 0
MB_OK equ 0
MB_ICONHAND equ 10h
MB_ICONASTERISK equ 40h
OPENFILENAME struc
Size dd ?
HOwner dd ?
AppHwnd dd ?
Filters dd ?
CustFilters dd ?
CstFltSize dd ?
CurFilter dd ?
CurFileName dd ?
CurFlNmSize dd ?
CurFile dd ?
CurFlSize dd ?
InitialDir dd ?
DlgTitle dd ?
Flags dd ?
FileOffset dw ?
ExtOffset dw ?
Extension dd ?
CustData dd ?
HookProc dd ?
TmplateRsc dd ?
OPENFILENAME ends
UPopen OPENFILENAME <?>
UPname db 513 dup (0)
UPfiltre db "EXE Files (*.EXE)",0h,"*.EXE",0h
db "All Files (*.*)",0h,"*.*",0h,0h
UPdefext db "exe",0h
OpenTxt db 'Choose a file to unPrivate ...',0
; Messages :
Titre db 'unPrivateEXE par Nody v0.01',0
No_Erreur db 'On dirait que ça s''est bien passé ...',0
Erreur_No_PE db 'Le fichier n''est pas au format PE !',0
Erreur_Open db 'Erreur pendant l''ouverture du fichier.',0
Erreur_No_Protec db 'Le fichier n''est pas protégé par PrivateEXE !',0
.CODE
; Début du CODE
START:
mov [UPopen.Size], 76
mov [UPopen.HOwner], 0
mov [UPopen.AppHwnd], 0
mov [UPopen.Filters], offset UPfiltre
mov [UPopen.CustFilters], 0
mov [UPopen.CstFltSize], 0
mov [UPopen.CurFilter], 1
mov [UPopen.CurFileName], offset UPname
mov [UPopen.CurFlNmSize], 512
mov [UPopen.CurFile], 0
mov [UPopen.CurFlSize], 0
mov [UPopen.InitialDir],0
mov [UPopen.DlgTitle], offset OpenTxt
mov [UPopen.Flags], 1800h
mov [UPopen.FileOffset], 0
mov [UPopen.ExtOffset], 0
mov [UPopen.Extension], offset UPdefext
mov [UPopen.CustData], 0
mov [UPopen.HookProc], 0
mov [UPopen.TmplateRsc], 0
push offset UPopen ; utilisation d'une CommonDialog
pour avoir
call GetOpenFileNameA ; le nom du fichier à
nettoyer
test eax, eax ; eax=0 si on appuie sur "annuler"
je Quitte
push 0
push 80h ; file_attribut_normal
push 3 ; open_existing
push 0
push 0
push GENERIC_READ or GENERIC_WRITE
push offset UPname
call CreateFileA ; ouverture du fichier
cmp eax, 0FFFFFFFFh ; eax = -1 si erreur
je Error_Open ; erreur !
mov [Fhandle], eax ; sauvegarde du handle du fichier
push 0
push eax
call GetFileSize
mov [FileSize], eax ; sauvegarde de la taille du
fichier
push 0
push 0
push 0
push 4 ; Read AND Write
push 0
push dword ptr [Fhandle]
CALL CreateFileMappingA ; on va mapper le fichier
en mémoire
cmp eax, 0FFFFFFFFh
je Error_Open
mov dword ptr [Hmem], eax
push 0
push 0
push 0
push 2 ; Read AND Write
push dword ptr [Hmem]
CALL MapViewOfFile ; 2eme API pour le mapping
test eax, eax
je Error_Open
mov dword ptr [addr_file], eax ; addresse de début
de mapping
mov esi, [addr_file] ; esi -> début du
mapping
xor edx, edx
add edx, dword ptr [esi+03Ch]
cmp edx, dword ptr [FileSize] ; pour etre sur que
le fichier
jge Erreur_No_PE_File ;
cmp word ptr [esi+edx], 'EP' ; soit au format PE
jne Erreur_No_PE_File ;
add edx, 28h
mov eax, dword ptr [esi+edx]
mov [EntryPoint], eax ; sauvegarde de l'EntryPoint
du prog protégé
add edx, 0Ch
mov eax, dword ptr [esi+edx]
mov [ImageBase], eax ; sauvegarde de l'ImageBase
mov [RVA_EP], eax
mov eax, dword ptr [EntryPoint]
add [RVA_EP], eax ; offset de début de la
section 007(Relative Virutal Adress)
; recherche du section header de la section .007 :
xor ecx, ecx
add ecx, dword ptr [esi+03Ch]
add ecx, 0F8h ; on passe le PE header qui fait 0xF8
octets
Search_Section :
cmp dword ptr [esi+ecx], 3730302Eh
je Section_Found
add ecx, 28h ; chaque section header fait 0x28 octets
cmp ecx, 1024
jle Search_Section
jmp Error_Bad_File ; impossible de trouver le section
header
Section_Found :
mov [Section], esi
add [Section], ecx ; addresse de la section .007
mov eax, dword ptr [Section]
add eax, 16
mov eax, dword ptr [eax]
mov [SizeofRawData], eax ; taille de la section
.007
add esi, dword ptr [FileSize]
sub esi, dword ptr [SizeofRawData] ; esi pointe
vers la secction .007
cmp dword ptr [esi+122h], 37303000h ; pour vérifier
jne Error_Bad_File ;
add [RVA_EP], 6
add esi, 0Ah
mov eax, dword ptr [esi]
sub dword ptr [RVA_EP], eax
mov eax, dword ptr [ImageBase]
sub dword ptr [RVA_EP], eax ; l' EntryPoint original
!!
mov esi, [addr_file]
xor edx, edx
add edx, dword ptr [esi+03Ch]
dec dword ptr [esi+edx+6] ; restaure le nbre de
sections
add edx, 28h
mov eax, dword ptr [RVA_EP]
mov dword ptr [esi+edx], eax ; restaure l'EntryPoint
add edx, 28h
sub dword ptr [esi+edx], 1000h ; restaure l'ImageSize
mov esi, offset Buffer
mov edi, dword ptr [Section]
mov ecx, 28h
repe movsb ; efface le section header de la secction
.007
mov eax, dword ptr [SizeofRawData]
sub dword ptr [FileSize], eax ; nombre d'octets
à écrire
push 0
push offset NbBytesWritten
push dword ptr [FileSize]
push [addr_file]
push [Fhandle]
call WriteFile ; sauvegarde des modif'
push 0
push 0
push [FileSize]
push [Fhandle]
call SetFilePointer
push [Fhandle]
call SetEndOfFile ; change le EndOfFile
push [Fhandle]
call CloseHandle ; ferme le fichier
push [addr_file]
call UnmapViewOfFile ; libère la mémoire
push MB_OK or MB_ICONASTERISK
push offset Titre
push offset No_Erreur
push 0
call MessageBoxA ; pas d'erreur !
jmp Quitte
; Les Messages d'erreur :
Error_Bad_File :
push [Fhandle]
call CloseHandle
push [addr_file]
call UnmapViewOfFile
push MB_OK or MB_ICONHAND
push offset Titre
push offset Erreur_No_Protec
push 0
call MessageBoxA
jmp Quitte
Erreur_No_PE_File :
push [Fhandle]
call CloseHandle
push [addr_file]
call UnmapViewOfFile
push MB_OK or MB_ICONHAND
push offset Titre
push offset Erreur_No_PE
push 0
call MessageBoxA
jmp Quitte
Error_Open :
push MB_OK or MB_ICONHAND
push offset Titre
push offset Erreur_Open
push 0
call MessageBoxA
Quitte :
PUSH 0
CALL ExitProcess ; bye !
END START
Note : je ne suis pas responsable de vos actes, alors si vous avez des ennuis parce que
vous avez utiliser mon programme, et bien démerdez-vous !
nodless@usa.net
|