Protection   PrivateEXE v 2.0a  

Les Protections Renforcées

Outils   ProcDump Wdasm SoftIce    
Cible   PrivateEXE  

By Nody

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