|
Compression et Encryption:
Il faut bien distinguer le compresseur (packer) qui va réduire la taille d'un l'exécutable (ou d'une
DLL...) tout en lui permettant de rester exécutable, du crypteur qui est beaucoup plus un élément
du système de protection d'un programme.
Voici deux exemples:
exemple de décompression:
0010 8BE8 MOV BP,AX
0012 8CC0 MOV AX,ES
0014 051000 ADD AX,0010
0017 0E PUSH CS
0018 1F POP DS
0019 A30400 MOV [0004],AX
001C 03060C00 ADD AX,[000C]
0020 8EC0 MOV ES,AX
0022 8B0E0600 MOV CX,[0006]
0026 8BF9 MOV DI,CX
0028 4F DEC DI
0029 8BF7 MOV SI,DI
002B FD STD
002C F3 REPZ
002D A4 MOVSB
002E 50 PUSH AX
002F B83400 MOV AX,0034
0032 50 PUSH AX
0033 CB RETF > Instruction RETF
0034 8CC3 MOV BX,ES
0036 8CD8 MOV AX,DS
0038 48 DEC AX
0039 8ED8 MOV DS,AX
|
Notez l'instruction RETF. Cette instruction est rencontrée au début
de codes comme ceux ci, et indiquent presque toujours que le fichier est compressé.
Exemple de décryption:
8AA511274000 MOV AH,[EBP+00402711] > Chaîne à traiter AC
LODSB > charge EAX avec le contenu de DS:SI
32C4 XOR AL,AH > ou logique entre AL(E8h) et AH
FEC4 INC AH > ajoute 1 à AH
C0C402 ROL AH,02 > rotation à gauche de 2
80C490 ADD AH,90 > ajoute 90h à AH
AA STOSB > transfère double mot par double mot
> le contenu de EAX dans ES:DI
E2F2 LOOP 0040DA81 > et ce tant que CX est <> de 0 (boucle)
|
Le principe le plus simple d'encryption est le Xor AL, AH, ou AH est la clé
de cryptage. Ensuite, toutes les variantes sont possibles pour modifier la valeur de la clé de cryptage
qui pourra être différente à chaque boucle.
Principe général:
L'architecture d'un programme crypté/compressé va être
la suivante:
1- Le Loader
2- Le code crypté ou compressé.
Le Loader, qui par la force des choses, est toujours "clean" (comprenez ni crypté, ni compressé)
va s'occuper de la décompression/décryption.
Dans certain cas, vous pourrez avoir un loader qui décode un second loader qui décrypte les codes
traités en trois fois: c'est le cas par exemple de Genius 2.7, crypté par ASPack. Ce crypteur fonctionne
toujours en trois différentes passes: il crypte une première fois l'exécutable d'origine,
re-crypte le résultat obtenu, et le re-crypte encore une fois (si bien que vous trouverez trois signatures
PUSH EAX/RET, dont seule la première sera dans une zone clean). Pour faire bon poids - bonne mesure, le
loader a lui-même été crypté, dans le cas de Genius 2.7, rendant les choses légèrement
plus délicates.
Vous pouvez aussi rencontrer des programmes qui ont été une première fois comprimés
(par exemple par UPX) puis ensuite cryptés (par exemple par PE Encrypter), comme c'est le cas pour le Drapeau
Noir n°5 de TaMaMBoLo.
De la même façon, certains compresseurs acceptent de fonctionner malgré de multiples (2 à
3 passes) compressions.
Le code à décrypter pourrait se décomposer en trois types de codes:
- Les codes n'ayant aucun sens lorsque vous tombez dessus dans un Dead listing, ou ceux-ci sont interprétés
au mieux, et ou les add byte ont la part belle.
- Les longues séries de add dword ptr
- Les longues séries de BYTE 10 DUP(0)
:004233B1 E9E760FEFF jmp 0040949D
:004233B6 CC int 03
:004233B7 CC int 03
:004233B8 8B4DF0 mov ecx, dword ptr [ebp-10]
:004233BB E9B15BFEFF jmp 00408F71
:004233C0 B870A44200 mov eax, 0042A470
:004233C5 E9D360FEFF jmp 0040949D
:004233CA 0000 add byte ptr [eax], al
:0041D026 0100 add dword ptr [eax], eax
:0041D028 800000 add byte ptr [eax], 00
:0041D02B 0030 add byte ptr [eax], dh
:004064B2 00000000000000000000 BYTE 10 DUP(0)
:004064BC 00000000000000000000 BYTE 10 DUP(0)
:004064C6 00000000000000000000 BYTE 10 DUP(0)
:004064D0 00000000000000000000 BYTE 10 DUP(0)
Présence d'un compacteur ou d'un crypteur: les symptômes.
Au delà, et bien avant que vous ne puissiez accéder au dead Listing, les programmes packés/compressés
se distinguent par les symptômes suivants:
- Au lancement de l'application, via le Symbol Loader de SoftIce, vous n'avez pas de break sur l'Entry point .
- Wdasm ne désassemble pas le fichier (accompagné souvent du message "the PE Files is not a
Standard Window Format), ou ne désassemble que le Pe header (donc pas de codes). Vous obtiendrez quand même,
dans ce cas, une information qui sera précieuse pour la suite: l'entry point du programme dans sa version
cryptée/compressée
+++++++++++++++++++ ASSEMBLY CODE LISTING ++++++++++++++++++
//********************** Start of Code in Object CODE **************
Program Entry Point = 00559CC8
(ASPack) -> Entry point de l'exe crypté
Dans le meilleur des cas, Wdasm désassemble le fichier sans les ressources
habituelles (Strings Datas, Fonctions importées et exportées, Dialog...).
- La taille de l'exécutable, sachant que les applications Windows manquent singulièrement de discrétion
à ce niveau, est également un bon indicateur de la présence éventuelle d'un compresseur
(une application inférieure à 100 ko, aujourd'hui, devient rare...).
Identification du compresseur ou du crypteur:
Suivant le crypteur/compresseur utilisé, celui ci va être plus ou moins discret et/ou repérable.
Dans les différentes solutions envisageables pour découvrir à qui vous aurez affaire, vous
pouvez:
· utiliser un File Analyser (Type PE Sniffer)
· chercher dans les codes du programme soit un copyright (souvent vers la fin du fichier),
.......$Id: UPX
0.82 Copyright (
C) 1996-1999 Las
zlo Molnar & Mar
kus Oberhumer $
|
. ..............En
crypted by Stone
CF - PowerLame
PE-ExeEnCrypter
! :) 2nd&mi ...
|
... -=. PE-SHiELD v0
.2 -.- (C) Copyright
1998 by ANAKiN [DaV
inci] .=- ..........
|
Soit des noms de sections que vous identifierez comme étant liés à
un packer/crypteur (en début de fichier, au niveau des informations du PE Header), en utilisant un Snooper
pour récupérer toutes les chaînes alphanumériques du fichier, ou plus simplement un
éditeur hexadécimal (avec correspondance ASCII, mais il me semble qu'ils le font tous...).
....PESHiELD........
.V..................
@...PESHiELD........
.....Z..............
@...ANAKIN98.0......
, ou avec ProcDump,
UPX0 00038000 00001000 00000000 00000400 E0000080
UPX1 00023000 00039000 00022600 00000400 E0000040
.rsrc 00001000 0005C000 00000C00 00022A00 C0000040
· Identifier la signature du packer/compresseur, c'est à dire le code
qui permettra de reconnaître tel ou tel packer, à partir d'une suite de mnémoniques caractéristiques
(ex 61 FF E0 = POPAD JMP EAX) et bien sûr situés dans une zone non compressée/packée
(clean) .
Citons quelques Patcher/Compresseur connus:
| NOM |
Type |
Signature |
© |
Section |
| ASPack |
Crypteur |
E9 |
Oui |
.udata |
| Code Safe 3.X |
Crypteur |
89 04 8A |
Oui |
??? |
| Neolite |
Packer |
FF E0 80 3D |
Oui |
.neolite |
| PEPack |
Packer |
61 FF E0 |
Oui |
.PEPACK |
| Petite 2.1 |
Packer |
8B EC 88 |
Oui |
. petite |
| Shrinker 3.x |
Packer |
FF 75 10 FF 75 0C FF 75 08 FF 55 |
Oui |
.shrink |
| STNE 1.13 |
Packer |
FE 00 |
??? |
??? |
| UPX |
Packer |
61 E9 |
Oui |
UPX |
| VG Crypt |
Crypteur |
E9 CB |
aucun |
aucune |
| WWPack |
Packer |
5D 5B E9 |
Oui |
.WWP32 |
Utiliser Wdasm et SoftIce:
Modifier les caractéristiques des sections:
Pour pouvoir utiliser l'un et/ou l'autre, il va falloir faire une modification au niveau de l'une (ou de l'ensemble)
des sections. Pour cela, vous pouvez utiliser un outil comme ProcDump.
.text RVA: 00001000 Offset: 00001000 Size: 00003000 Flags: C0000040
.data RVA: 00005000 Offset: 00004000 Size: 00001000 Flags: C0000040
.idata RVA: 00006000 Offset: 00005000 Size: 00001000 Flags: C0000040
.rsrc RVA: 00007000 Offset: 00006000 Size: 00006000 Flags: C0000040
.reloc RVA: 0000D000 Offset: 0000C000 Size: 00001000 Flags: C0000040
.PEPACK!! RVA: 0000E000 Offset: 0000D000 Size: 00001000 Flags: C0000040
Vous aurez probablement remarqué que dans cet exemple, toutes les sections
ont C0000040 comme caractéristique (c'est une constante, car il faut que la section soit Readable, Writable
et Data! ainsi, le programme peut lire le code compressé, et le décompresser dans la même section
sans provoquer d'erreur fatale dans Windows), ce qui, normalement, vous empêche de désassembler l'exécutable,
ou d'obtenir un break en passant par le Symbol Loader de SoftIce.
En modifiant la première section (.text), et en remplaçant C0000040 par E0000020 à l'aide
de ProcDump, vous pourrez reprendre la main avec SoftIce, et obtenir un listing, sans les ressources (Strings Data...)
dans Wdasm.
Voici quelques explications rapides et très succinctes sur les attributs des sections:
================
0x20...... : signifie eXecutable
0x40...... : signifie Readable
0x80...... : signifie Writeable
exemple 0x60.. -> eXecutable + Readable
0x60... -> R-X-.
0xC0... -> R-.-W
0xE0... -> R-X-W
=================
0x......20 : signifie contains Code
0x......40 : signifie Initialized data
0x......80 : signifie Unitialized data
0x......40 : la plupart des sections peuvent avoir cette valeur excepté les sections CODE et .BSS
=================
Et plus simplement:
0x00000020 IMAGE_SCN_CNT_CODE
0x20000000 IMAGE_SCN_MEM_EXECUTE
0x40000000 IMAGE_SCN_MEM_READ
OR 0x80000000 IMAGE_SCN_MEM_WRITE
-----------------------------------
0xE0000020
Voici comment vous y prendre pour modifier les caractéristiques des sections:
Dans ProcDump, en cliquant sur le bouton PE EDITOR, vous allez obtenir des informations sur le Soft compressé
:
L'image base qui semble toujours être 400000 et la valeur de l'Entry point.
En cliquant sur le bouton " Sections ", vous obtiendrez le tableau suivant :
.shrink0 00261000 00001000 00000000 00000000 80000A82
BSS 00001000 00262000 00000000 00000000 C0000000
.shrink1 00009000 00263000 00000000 00000000 80000A82
.tls 00000008 0026C000 00000000 00000000 C0000000
.rdata 00000018 0026D000 00000200 00000400 50000040
.shrink2 0014A000 0026E000 00000000 00000000 80000A82
Un right-clic va vous permettre d'accéder à un nouvel écran
où vous pourrez modifier les sections.
Il arrivera parfois que, malgré ces modifications, vous ne réussissiez pas à prendre la main.
Il a différentes méthodes pour y arriver.
Création d'un Dump:
Pour pouvoir disposer d'un listing avec les ressources, il va falloir créer un dump de l'exécutable
compacté/crypté. ProcDump est sensé pouvoir le faire de façon automatique:
Cliquez sur UNPACK et une seconde boite vous proposera de choisir l'une des protections reconnues par le fichier
Script.ini. A défaut, vous pouvez opter pour UNKNOW, mais les résultats sont beaucoup plus aléatoires.
ProcDump va lancer l'application/cible, et en réaliser une copie-mémoire décompressée.
L'écran qui s'affiche pendant ce temps va donner certains premiers éléments, dont l'adresse
de l'Entry point de la cible, et l'adresse où il aura trouvé les octets qui lui permettront d'identifier
la protection. (Cf le Script.ini). L'écran en question vous donnera les informations suivantes:
| Breakpoint reached at 0x007C83DA |
Entry Point du programme crypté |
| Setting breakpoint at 0x007C84D6 |
signature trouvée à 0x007C84D6 |
| Breakpoint reached at 0x007C84D6 |
|
| Process Successfully unpacked (EIP 0x007C84DF) |
passe la main au programme d'origine |
Mais dans certain cas l'opération peut n'être pas possible (une faiblesse
du script de ProcDump, un compresseur non identifié...).
La première étape va être de tracer l'exécutable à partir de l'Entry Point, et
trouver le moment où la décompression/cryption est terminée.
Dans les cas ou, malgré la modification des caractéristiques des sections, vous n'arrivez pas à
prendre la main, il y a d'autres solutions: si Wdasm vous a livré l'entry point de l'exécutable,
vous pouvez utiliser un éditeur hexadécimal (type Hiew) pour vous caler sur la première instruction
de l'exécutable,et
Prendre la main grâce à l'Interruption 3 :
L'idée étant d'obliger SoftIce à marquer un break, à l'aide de Hiew remplacez la valeur
qui se trouve à l'Entry Point.(Souvent un opcode 60 qui correspond au mnémonique PUSHAD) par CC,
le code pour INT 3.
Posez ensuite un BPINT 3 dans SoftIce, et lancez le programme
Remplacez immédiatement le code CC par sa valeur d'origine, (par exemple PUSHAD) sinon c'est le plantage
assuré :
60 PUSHAD -> entry point d'ASPack
E800000000 CALL 00681006
5D POP EBP
Et passez à l'étape suivante.
Un autre solution:
BPX GetProcAddress
Une majorité d'applications font appel à cette API dés leur lancement.
Dans la majorité des cas, vous allez pouvoir vous retrouvez dans SoftIce grâce à ce breakpoint
…
Trouver le moment où la décompression est terminée.
Un grand nombre d'encrypteurs/compresseurs commencent par sauvegarder le contenu des registres (Pushad) avant de
commencer leur boulot, et les restituent à la fin de celui ci (Popad). Bien souvent, dans ce cas, vous aurez
le passage de relais du Loader à l'application d'origine, dans les parages.
PEPack
61 POPAD > restauration
FFEO JMP EAX > passe la main
8D85CE050000 LEA EAX,[EBP+000005CE]
PE-ExeEnCrypter
5F POP EDI > restauration
5D POP EBP > restauration
FFE0 JMP EAX > passe la main
Voici une variante du POPAD, ou les registres sont récupérés
un à un:
STNE 1.13
015f:0040e090 pop ebx ------on récupère
015f:0040e091 pop ecx ------tous les
015f:0040e092 pop edx ------registres
015f:0040e093 pop esi
015f:0040e094 pop edi
015f:0040e095 pop ebp
015f:0040e096 jmp eax ----- et on saute vers le prog décrypté...
VG Crypt
61 POPAD > restauration
9D POPFD
8B9A09274000 MOV EBX,[EDX+00402709]
898A09274000 MOV [EDX+00402709],ECX
FFE3 JMP EBX variante du EAX! > passe la main
Avec un peu d'habitude, on en sait assez: Le JMP EAX (ou plus rarement EBX) est
très couramment utilisé pour brancher vers les codes du programme original (OEP).
Toujours dans les variantes, vous avez le call EAX, parfois déguisé, comme dans le cas de Shrinker:
8945E0 mov dword ptr [ebp-20], eax (le fameux EAX!)
FF7510 push [ebp+10]
FF750C push [ebp+0C]
FF7508 push [ebp+08]
FF55E0 call [ebp-20] > passe la main à l'OEP
8945E4 mov dword ptr [ebp-1C], eax
EB07 jmp 0063E4EE
Là aussi, il y a des variantes: le push EAX (qui va pousser une valeur sur
la pile) et le Ret (qui récupère cette adresse sur la pile) vont simuler un retour sur call appelant.
Aspack
LEA EAX,[EBP+00444C37]
50 push eax
C3 ret
Il y a aussi les cas ou plus simplement le programme se branche sur l'Entry Point
du programme décompressé/décrypté par un tout bête JMP OEP (Original Entry Point)
UPX:
61 popad
E98EF7CAFF jmp 004CAD80 > jump sur l'OEP
00000000000000000000 BYTE 10 DUP(0)
00000000000000000D42 BYTE 10 DUP(0)
(Il n'est pas très difficile de mettre la main sur la signature d'UPX. Toutes
les versions que j'ai pu en voir permettent de la trouver à la fin du listing désassemblé)
Neolite
5D pop ebp
5B pop ebx
E9673DFFFF jmp 00401000 > jump sur l'OEP
Il y a bien sur des cas particuliers:
Petite 2.1
Contrairement à d'autre compresseur, le relais est passé comme ceci, après avoir bouclé
4 fois sur l'adresse 0040D1D6. C'est au quatrième passage que l'Entry Point du programme d'origine sera
présent:
0137:0040D1D3 55 PUSH EBP
0137:0040D1D4 8BEC MOV EBP,ESP
0137:0040D1D6 8807 MOV [EDI],AL > passe la main
0137:0040D1D8 81ECD8BA0000 SUB ESP,0000BAD8
0137:0040D1DE 8D8D887FFFFF LEA ECX,[EBP+FFFF7F88]
Les codes Polymorphes ou L' overlapping:
Il peut arriver que le traçage dans SoftIce soit gêné par des Codes Changeant d'Apparences
(CCA):
Softice désassemble une instructions d'offset EIP (disons que le nombre de bytes qu'elle fait est appellé
NBI), puis l'instructions positionnée à EIP+NBI....
Il est donc assez facile a faire du CCA.
Imaginons la séquence suivante:
jmp @1
db E8h
@1:
mov eax,[eax]
inc eax
inc edx
En mémoire vous aurez quelque chose comme
EB01
E8
8B004042
mais E8 est le byte qui commence un opcode du style "call offset" donc ce code, s'il est désassemblé
par adresse croissante donnera
EB01 jmp @1
E88B004042 call addresse
Lorsque vous tracerez en sautant le jump, vous tombez au milieu de l'opcode du call, et softice re-désassemblera
cette instruction ce qui donnera l'impression que le code change d'apparence -> CCA
Vous avez sûrement compris qu'il est donc très facile de faire ce style de code en ajoutant différents
types de bytes. Au lieu de E8h on peut ajouter 0CDh,020h qui correspond à une int 20h -> VMMJump qui
utilise le DWORD suivant l'opcode... et SoftIce affichera donc un truc du style:
int 20 VXDJump XXXX,XXXX
avec XXXX,XXXX la valeur contenue par le DWORD suivant l'int 20.... "
Au cas ou vous ne l'auriez pas compris, ce type de codage est particulièrement long, pénible, et
fatiguant à tracer (pour ne pas dire….), et demande à être tracé TRES doucement, puisque
vous ne voyez pratiquement jamais quelle va être la VRAIE instruction suivante.
Pour avoir déjà rencontré ce type de codage, a peu prés TOUS les Call adresse doivent
être tracés Step Into (F8).
Une solution pour tracer les CCA est de le faire "à l'ancienne" en regardant les adresses mémoires
sur la gauche de l'écran, et dès qu'il y a un saut assez conséquent vous saurez que le programme
a passé la main et que le packer a finit son travail
Un autre inconvénient majeur des CCA, et la difficulté de poser des BreakPoints on eXecution, SoftIce
n'acceptant pas de poser ses INT3 au milieu d'un code. Les BPM adresse X donnent de bons résultats, mais
sont limités à quatre.
Dumper le programme en mémoire:
Lancez le programme/cible via le Symbol Loader de Softice . De cette façon SoftIce pourra prendre la main.
Posez un bpx sur le jmp eax (ou son équivalent), et faites [F5] pour relancer le programme. Au break, passez
en mode assemblage, et changez le jmp eax par un jmp eip.
Ainsi, vous obligerez le programme à boucler sur lui-même.
Rouvrez ProcDump, et cliquez sur " Options " pour choisir " Rebuilt Import Table ", puis sélectionnez
l'application dans la liste des taches actives de la fenêtre de ProcDump. Après quelques instants,
vous pourrez sauver un joli dump du nouvel exécutable prêt a être patché si l'envie vous
en prend, avec du beau code tout clean.
A lire: création d'un Dump pour un programme Tri-ASPacké: Advanced Registry Tracer by aRTex
Rendre le fichier obtenu exécutable:
Le zoli exécutable obtenu ne va pas pour autant fonctionner. Il faut encore lui indiquer quel va être
son nouvel Entry Point. C'est encore ProcDump qui va nous permettre de réaliser facilement cette modification.
Ouvrez ProcDump, et choisissez "PE EDITOR"
Header Infos
Entry Point : 00240E98
Size of image : 003B8000
Image Base : 00400000
L'entry point dont vous aurez relevé l'adresse dans SoftIce (le contenu de
EAX par exemple) va devoir subir une petite opération…
Supposons que cet Original Entry Point soit un classique 00401000. Il va falloir lui soustraire la valeur de l'imagebase
(00400000): 401000 (OEP) - 400000 (IB) = 1000 (Entry Point à entrer dans le champ de ProcDump).
Si tout c'est bien passé, votre exécutable est maintenant Runnable!
.
Patcher l'exécutable:
Une fois obtenu un Dump exécutable du programme, vous aurez, normalement, une copie de ce qu'il pouvait
être avant de passer dans le compresseur/crypteur. Par contre vous aurez aussi, dans le cas des compresseurs,
un programme ayant une taille nettement plus importante que le fichier packé (attention au CheckSum!).
Vous pouvez alors opter pour l'utilisation d'un Memory Patcher:
R!SC's Process Patcher v1.2 :
R!SC est un Memory Patcher qui utilise un script pour créer un fichier exécutable de 8 ko, et qui
permettra de lancer l'exécutable sans le modifier " physiquement " (et donc sans avoir à
s'inquiéter d'éventuel Checksum), mais qui interviendra quand le programme sera décompressé
en mémoire.
Voici un exemple de script qu'il serait possible d'écrire pour Genius 2.7:
T=2000: ; temps accordé pour trouver les bytes à modifier
F=genius2.exe: ; nom du programme à patcher
O=loader_genius.exe: ; nom du fichier exe que vous allez créer
P=5515BA/8A,5E,2A/B3,01,90: ; modifications à apporter en 005515BA
$ ; ordre de fin de script
Mais les puristes vous diront que ce n'est pas encore suffisant. Il va falloir réussir
à patcher le programme compressé et/ou crypté. Pour y arriver, vous devez commencer par:
Ecrire le Patch
La trame des modifications à apporter, dans le cas de programmes compressés/cryptés sont les
suivantes:
1- A partir d'une zone clean, après décompresion/décryption, et en général avant
ou sur le JMP EAX/RET: Saut vers le patch
2- Au besoin, vérification que l'adresse (ou les adresses) visée est bien decompressée/décryptée
3- Modification des octets (attention: une inscription en mémoires supérieure à 8 bits se
fait " à l'envers ", d'ou par exemple un B001 (mov bl,01) s'écrirai 01B0).
4- Restauration des octets écrasés par le saut_vers_le_patch, et reprendre le cours des choses...
Il arrivera (comme dans le cas d'ASPack, ou de tous les programmes multi-compressés/cryptés), que
vous soyez obligé d'écrire plusieurs patchs, un patch pour chaque étape de la décompression/décryption:
Patch 1: modifie un JmpXXXXXXXX par le JMP patch_3
C70515234400E9XXXXXXMOV DWORD PTR [00442315],XXXXXXE9 > Force le saut vers le patch2
C60519234400XX MOV BYTE PTR [00442319],XX
6800800000 PUSH 00008000 > restaure les octets écrasé
E9XXXXXXXX JMP 00XXXXXX > et continue
Patch 2: test si la zone à patcher est clean, et place un jmp patch_4
803D8BE76D00C2 CMP BYTE PTR [006DE78B],C2 > >la zone est elle décompressée
7511 JNZ 00XXXXXX > si non-> saute, si oui:
C7057DE76D00E9XXXXXXMOV DWORD PTR [006DE77D],XXXXXXE9 > prépare le saut suivant vers patch3
C60581E76D00XX MOV BYTE PTR [006DE781],XX
E9XXXXXXXX JMP 00XXXXXX > et continue
Patch 3: test si la zone est clean et place un patch_4
803DF0FF6C0055 CMP BYTE PTR [006CFFF0],55 > la zone est elle décompressé
7518 JNZ 00442F9C > si non-> saute, si oui:
C605F0FF6C00C3 MOV BYTE PTR [006CFFF0],C3 > remplace 55 par C3
C70593266C00E9XXXXXX MOV DWORD PTR [006C2693],XXXXXXE9 > prépare le saut vers patch4
C60597266C00XX MOV BYTE PTR [006C2697],XX
E9XXXXXXXX JMP 00XXXXXX > et reprend le boulot
Patch 4:
803D3269400033 CMP BYTE PTR [00406932],33 > la zone est elle décompressée
750A JNZ 00442FB4 > si non-> saute, si oui:
66C705326940004040 MOV WORD PTR [00406932],4040 > remplace 33C0 par 4040
83E103 AND ECX,03 > restaure les octets modifiés
F3A4 REPZ MOVSB > idem
E9XXXXXXXXX JMP 006C2698 > retour à la normale.
Une fois que vous connaissez la taille du patch, il ne reste plus qu'à,
Trouver de la place
Pour pouvoir y glisser les quelques lignes de votre patch.
Bien souvent, par facilité, je m'installe sur un petit bout de l'icône, ou à la place des informations
de versions ou de copyright. Dans certain cas, même ceux ci ont été codés, et un outil
comme les Borland Ressources WorkShop ne pourra pas vous aider à trouver la chaîne correspondant à
l'icône convoitée. La seconde solution va consister à trouver un espace disponible.
Partant du principe qu'un compileur complète toujours la fin d'une section par des octets nuls (00 00),
vous allez descendre dans le listing de SoftIce avec l'ascenseur de la fenêtre des codes, ou regarder si
vous avez de la place à la fin de la dernière section de l'exécutable avec un Hex-éditeur,
à la recherche de ce type de codes:
0177:0040EFF7 0000 ADD [EAX],AL > octets nuls
0177:0040EFF9 0000 ADD [EAX],AL
0177:0040EFFB 0000 ADD [EAX],AL
0177:0040EFFD 0000 ADD [EAX],AL
0177:0040EFFF 00FF ADD BH,BH
0177:0040F001 FFFF INVALID
0177:0040F003 FFFF INVALID > fin de la section
Il faudra, avant tout, vous assurez qu'à aucun moment le compresseur/crypteur
ne va venir y écrire quoi que ce soit, ou l'utiliser pour Dieu sait quoi, en posant quelques BPM sur les
adresses repérées.
Mais les "méthodes" abordées ci dessus, même si elles se révèlent souvent
suffisantes, sont assez empiriques, sinon hasardeuses…
Pour faire "plus sérieux", voici quelques autres solutions:
· pour rajouter le code à la fin du code du programme compressé, vous devrez retourner dans
le PE Header:
Pour rappel, tout programme est divisé en sections, et chaque section possède une entrée dans
le PE Header, ca veut dire que le loader possède une section dans le PE Header. ProcDump va nous permettre
de connaître les détails de cette section ( celle contenant l'entry point):
Par exemple : (.data étant la section qui comprend l'EntryPoint et donc le Loader )
Virtual size Virtual Offset Raw size Raw Offset
.data 00002000 000DF000 00001400 00050A00
La Virtual size de la section est 00002000, ce qui veut dire que la mémoire
qui sera allouée pour cette section sera de 00002000 bytes. ( Cette valeur est toujours un multiple du Virtual
Offset de la section CODE. ) Le Virtual Offset de la section, et donc du code appartenant au loader, est de 000DF000,
additionné à l'Image base qui vaut 00400000, ca nous donne une adresse virtuelle de 004DF000.
La taille sur disque (Raw Size) est de 00001400, ce qui veut dire que le code n'utilise que 00001400 bytes dans
le fichier .exe. L'offset sur disque (Raw Offset) est de 00050A00, autrement dit le code s'étale sur le
disque de l'offset 0050A00 jusqu'a 0050A00+1400 = 00051E00.
Rem: Vu que la taille doit être un multiple, cela veut dire que le compilateur rajoute des 00 pour que la
section ait la bonne taille.
Généralement, après les informations de versions, il y a beaucoup de 00 que vous pouvez modifier.
Mais, et oui, il y a un mais, il faut faire attention à une chose quand vous faites cela : Il faut bien
vérifier que l'adresse où vous allez écrire le code de votre patcher fait toujours partie
de la section que vous voulez modifier.
Rem: Il vaut mieux écrire le patch à la place de 00 que sur du code, même si celui-ci semble
inutile au premier coup d'śil.
Agrandir une section:
Dans le projet sur les PE Packed, il est parfois impossible d'insérer un patch. Il faut alors rajouter du
code ou, en d'autre terme, augmenter la grandeur d'une section, voir rajouter une section. Nous commencerons par
augmenter une section de 0x1000 bytes ( assez pour écrire un patch je crois!) et nous terminerons par le
rajout manuel d'une section à la fin du fichier.
Pour cet essai, je vais augmenter la dernière section ( il est préférable de modifier la dernière
section car sinon de nombreux problèmes d'adresse vont apparaître). Jetons d'abord un śil aux différentes
caractéristiques des sections de NotePad.exe:
Disassembly of File: Notepad.exe
Code Offset = 00001000, Code Size = 00004000
Data Offset = 00005000, Data Size = 00001000
Number of Objects = 0005 (dec), Imagebase = 00400000h
.text RVA: 01000 VSize: 03E9C RawOffset: 01000 RawSize: 04000 Flags: 60000020
.data RVA: 05000 VSize: 0084C RawOffset: 05000 RawSize: 01000 Flags: C0000040
.idata RVA: 06000 VSize: 00DE8 RawOffset: 06000 RawSize: 01000 Flags: 40000040
.rsrc RVA: 07000 VSize: 06000 RawOffset: 07000 RawSize: 06000 Flags: 40000040
.reloc RVA: 0D000 VSize: 00A9C RawOffset: 0D000 RawSize: 01000 Flags: 42000040
l'Offset de chaque section doit être un multiple de l'Offset du Code. Ici
: 00001000.
Notre but est donc d'augmenter la grandeur de .reloc de 0x1000 pour atteindre une RawSize égale à
00002000
On modifiera donc aussi VSize à 0x2000 afin de pouvoir utiliser la place rajoutée
.
Now the file-header, which will start at byte 0x44 and is 0x14 bytes long:
Machine 4C 01 ; i386
NumberOfSections 02 00 ; code and data
TimeDateStamp 00 00 00 00 ; who cares?
PointerToSymbolTable 00 00 00 00 ; unused
NumberOfSymbols 00 00 00 00 ; unused
SizeOfOptionalHeader E0 00 ; constant
Characteristics 02 01 ; executable on 32-bit-machine
PointerToSymbolTable : 00000000
Maintenant il faut regarder les différentes valeurs des Directories :
Now we set up the data directories, beginning at byte 0xb8 and being 0x80 bytes long:
RawAddress RawSize
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXPORT (0)
?? ?? ?? ?? ?? ?? ?? ?? ; IMAGE_DIRECTORY_ENTRY_IMPORT (1)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_RESOURCE (2)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_SECURITY (4)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BASERELOC (5)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_DEBUG (6)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_TLS (9)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)
00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_IAT (12)
00 00 00 00 00 00 00 00 ; 13
00 00 00 00 00 00 00 00 ; 14
00 00 00 00 00 00 00 00 ; 15
00 60 00 00 8C 00 00 00 ; IMAGE_DIRECTORY_ENTRY_IMPORT (1) dans NotePad
00 70 00 00 8C 55 00 00 ; IMAGE_DIRECTORY_ENTRY_RESOURCE (2) dans NotePad
00 D0 00 00 3C 09 00 00 ; IMAGE_DIRECTORY_ENTRY_BASERELOC (5) dans NotePad
Il n'y aura donc rien à modifier aux valeurs des adresses des Directories
car la dernière section commence en 0000D000.
Sous HView, la dernière ligne de code de NotePad est celle-ci :
.0040DFF0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
Nous devons tout faire pour que cette dernière ligne soit après modification :
.0040EFF0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
Nous allons donc insérer 0x1000 bytes à la fin du fichier..
Pour ce faire, allez sous HexWorkShop et ouvrez Notepad.exe.
Situez votre curseur tout à la fin du fichier, cliquez avec le bouton droit de votre souris et sélectionnez
"Insérer". Mettez 1000 et cochez Hexa.
Faite OK et le code est à présent rajouté.
Sauvez et faite un Backup de Notepad.exe.
Si vous exécutez à présent le Notepad modifié, il n'y a aucuns problème. Nous
allons maintenant, à l'aide de ProcDump modifier les différentes caractéristiques de la dernière
section. Rappelons d'abord les différentes infos de cette section :
.reloc RVA: 0000D000 VSize: 00000A9C RawOffset: 0000D000 RawSize: 00001000 Flags: 42000040
Lancez ProcDump, clickez sur PE Editor et sélectionnez le NotPad.exe modifié.
Clickez sur Section, puis Right-click sur .reloc et faite "modifier",.
Mettez PSize et VSize à 2000.
Maintenant, la dernière section à une grandeur de 0x2000 sur disque et en mémoire et possède
0x1000 bytes vierges à utiliser à notre convenance.
Rajouter une section:
Il faut tout d'abord connaître quelques informations sur le PE-Header ... (en-tête PE pour les anglophobes)
et on va voir ça en disséquant le Bloc-Notes de Windows (Window$ pour les windophobes).
Voici les premiers octets du Bloc-Notes :
4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 B8 00 00 00 00 00
00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 80 00 00 00
0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F 74 20 62 65 20
72 75 6E 20 69 6E 20 44 4F 53 20 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 50 45 00 00
En rouge, c'est le dword qui contient l'adresse du début du PE-Header (il est toujours
situé en 0x3C). Donc ici le PE Header commence en 0x00000080 (et pas en 0x80000000 !), je l'ai mis en bleu pour le situer et il commence
toujours par "PE". Entre deux, il y a du code qui ne nous intéresse pas (comme la compatibilité
avec le DOS, etc...) donc on ne va pas s'attarder dessus.
PE-Header
Voyons de plus près le fameux PE-Header ... (quoique je développe seulement les parties dont on aura
besoin)
50 45
00 00 4C 01 05 00
65 91 46 35 00 00 00 00 00 00 00 00 E0 00 0E 01 0B 01 03 0A 00 40 00 00 00 74 00 00 00 00 00 00 CC 10 00 00 00 10 00 00 00 50
00 00 00 00 40 00
00 10 00 00 00 10 00 00
04 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 E0
00 00 00 04 00 00 89 18 01 00 02 00 00 00 00 00 10 00 00 10 00 00
00 00 10 00 00 10 00 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 60 00 00 8C 00 00 00 00 70 00 00 8C
55 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 D0 00 00 3C 09 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 E0 62 00 00 40 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Le voilà le beau PE-Header qui est long de 0xF8 (248) octets. Pour la suite, je suppose que le PE-Header
commence à l'offset X. J'ai résumé avec des zolies couleurs les valeurs contenues dans le
PE-Header :
Signature : Ça commence par les lettres 'PE',
original non ? (location : X + 0, ici 0x4550)
Nombre de Sections de l'EXE : on développera les
sections plus tard... (location : X + 6, ici 0x0005)
Entry Point du programme : c'est là que l'exécution
du programme va démarrer (ce n'est évident pas une constante) Attention, c'est une R.V.A. ! (location
: X + 0x28, ici 0x000010CC)
ImageBase : c'est à cette adresse que l'image du
programme va être chargée en mémoire. (location : X + 0x34, ici : 0x00400000)
Les Alignements des sections et du fichier : 2 dword
développés ICI. (location : X + 0x38 pour les sections, X + 0x3C pour le fichier,
ici : 0x00001000 et 0x00001000)
Taille de l'Image : taille que va prendre l'image du fichier
une fois chargée en mémoire. (location : X + 0x50, ici : 0x0000E000)
Le reste du PE-Header n'est pas important pour les modifications qu'on aura à lui faire.
R.V.A. et Alignement :
R.V.A. : Relative Virtual Address.
En fait quand vous avez une R.V.A., vous n'avez pas la "vraie" adresse en mémoire mais vous devez
lui ajouter l'ImageBase pour avoir la vraie adresse. Exemple : ici la R.V.A. de l'EntryPoint est 0x000010CC, en
fait l'EntryPoint se situe en 0x000010CC + 0x00400000 (ImageBase) = 0x004010CC.
Vous pouvez vérifier avec le Symbol Loader de SoftIce que le Bloc-Notes démarre à cette adresse
(j'ai oublié de vous dire que j'avais Windows 98 donc les valeurs indiquées peuvent varier MAIS le
principe reste le même !).
Il y a 2 types d'Alignement : celui du fichier sur le disque dur et celui une fois chargé en mémoire.
Ici l'alignement est de 4096 octets (décimal) pour les deux. Si une section fait 2000 octets de long est
qu'elle commence à l'offset Y, la prochaine section ne se trouvera pas à Y + 2000 mais à Y
+ 4096. De même, si la section fait 6000 octets de long, la prochaine se trouvera à Y + 4096*2 (8192).
Donc, si l'alignement n'est pas le même pour le fichier sur disque dur et pour le fichier chargé en
mémoire, les adresses ne seront pas les mêmes quand vous éditez le fichier avec l'éditeur
hexa, que celles que vous verrez avec SoftIce !! Ici, le problème ne se pose pas car les 2 sont alignés
à 4096 octets. Aussi bien, Si l'alignement du fichier avait été de 512 octets, on peut l'aligner
sur 4096 octets car 4096 est un multiple de 512.
Section Header
Le Section Header contient les informations relatives aux différentes sections de l'EXE.
Voici le Section Header du Bloc-Notes :
2E 74 65 78 74 00 00 00 9C 3E 00 00 00 10 00 00 00 40
00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60
2E 64 61 74 61 00 00 00 4C 08 00 00 00 50 00 00 00 10
00 00 00 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0
2E 69 64 61 74 61 00 00 E8 0D 00 00 00 60 00 00 00 10
00 00 00 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40
2E 72 73 72 63 00 00 00 00 60 00 00 00 70 00 00 00 60 00 00 00 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40
00 00 40 2E 72 65 6C 6F 63 00 00 9C 0A 00 00 00 D0 00
00 00 10 00 00 00 D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 42
Cette partie du Section Header (je n'ai pas recopié les 2000 zéros qui suivent ...) contient 5 sous-sections
header relatives aux 5 sections de l'EXE (d'où les 5 couleurs).
Etudions de plus près la première section :
2E 74 65 78 74 00 00 00 9C 3E 00 00 00 10 00 00 00 40 00 00 00
10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60
(j'espère que les couleurs ne perturbent personne :-) )
Nom de la Section : 8 octets, ici il s'agit de ".text"
R.V.A. Virtual Address : adresse de la section quand
elle est chargée en mémoire. (ici 0x00001000)
Taille de la section : taille de la section quand elle n'est
pas chargée en mémoire ! Cette valeur est alignée sur l'alignement du fichier. (ici 0x00004000)
Adresse de l'image : adresse de la section quand le fichier
n'est pas chargé en mémoire. (ici : 0x00001000)
Caractéristiques de la section : sur 32 bits :
bit 5 = la section contient du code (1) ou pas (0)
bit 6 = la section contient des données (1) ou pas (0)
bit 7 = données non initialisées donc des zéros partout (1) ou alors des données initialisées
ie des constantes du programme (0)
bit 29 = accès en exécution de la section (1)
bit 30 = accès en lecture de la section (1)
bit 31 = accès en écriture de la section (1)
Modifs du PE-Header et du Section Header pour rajouter une section :
Il faut tout d'abord créer une nouvelle entrée dans le Section Header :
Name 2E 54 65 65 4A 69 00 00 ; ".CouCou"
VirtualSize 00 10 00 00 ; VirtualSize=RawSize=0x1000
VirtualAddress 00 E0 00 00 ; RVA = 0x0000E000
SizeOfRawData 00 10 00 00 ; VirtualSize=RawSize=0x1000
PointerToRawData 00 E0 00 00 ; RawOffset = 0x0000E000
PointerToRelocations 00 00 00 00 ; inutilisé
PointerToLinenumbers 00 00 00 00 ; inutilisé
NumberOfRelocations 00 00 ; inutilisé
NumberOfLinenumbers 00 00 ; inutilisé
Characteristics 20 00 00 60 ; code,exécutable,readable :)
Allons maintenant sous Hview et rajoutons le code de notre Section Header juste
après celui de .reloc :
00000210: 00 00 00 00-40 00 00 40-2E 72 65 6C-6F 63 00 00 @ @.reloc
00000220: 9C 0A 00 00-00 D0 00 00-00 10 00 00-00 D0 00 00 £ ð ð
00000230: 00 00 00 00-00 00 00 00-00 00 00 00-40 00 00 42 @ B
00000240: 2E 54 65 65-4A 69 00 00-00 10 00 00-00 E0 00 00 .CouCou Ó
00000250: 00 10 00 00-00 E0 00 00-00 00 00 00-00 00 00 00 Ó
00000260: 00 00 00 00-20 00 00 60-00 00 00 00-00 00 00 00 `
Maintenant que cela est fait, nous allons modifier les infos NumberOfSection du
FileHeader et SizeOfImage du OptionalHeader.
Grace à ProcDump, vous pouvez modifier l'ImageSize facilement, vous lui rajoutez 0x1000 donc 0000E000 devient
0000F000 et vous sauvez.
Pour NumberOfSection, si vous ne voulez pas le rechercher ( il est à 6 bytes de la signature PE ) vous pouvez
utiliser eXeScope et regarder quel est son offset.
Avec HView :
00000080: 50 45 00 00-4C 01 05 00-65 91 46 35-00 00 00 00 PE L eæF5 > 05 sections
00000090: 00 00 00 00-E0 00 0E 01-0B 01 03 0A-00 40 00 00 Ó @
Vous remplacez 05 par 06 ( car on a rajouté une section :).
Maintenant nous allons rajouter le code de la section ( soit 0x1000 bytes ) à la fin du fichier.
Pour cela vous utilisez HexWorkShop, vous ouvrez NotePad.exe, vous vous placez à la fin du fichier, vous
Right-Clickez et sélectionnez Insérer.
Vous mettez 1000 et cochez Hexa. Ok. Vous sauvez ! Vous lancez NotePad et tout fonctionne correctement ! :) Vous
pouvez vérifier avec ProcDump que tout est correct.
Disassembly of File
Code Offset = 00001000, Code Size = 00004000
Data Offset = 00005000, Data Size = 00001000
Number of Objects = 0006 (dec), Imagebase = 00400000h
.text RVA: 00001000 Offset: 00001000 Size: 00004000 Flags: 60000020
.data RVA: 00005000 Offset: 00005000 Size: 00001000 Flags: C0000040
.idata RVA: 00006000 Offset: 00006000 Size: 00001000 Flags: 40000040
.rsrc RVA: 00007000 Offset: 00007000 Size: 00006000 Flags: 40000040
.reloc RVA: 0000D000 Offset: 0000D000 Size: 00001000 Flags: 42000040
.CouCou RVA: 0000E000 Offset: 0000E000 Size: 00001000 Flags: 60000020
Et vous voici arrivé à la dernière étape,
Ré-adressage vers le patch:
Le choix de l'adresse à patcher en premier pour ré-adresser vers le patch 1 est lié à
la fin de la décompression/décryption. Suivant les cas, soit il vous faudra respecter la place occupée
par les codes d'origines (situés dans la zone clean du programme et trouvable par un hex éditeur),
soit vous pourrez déborder sur les adresses suivantes:
Codes d'origines:
0177:006810A5 8BB5B5504400 MOV ESI,[EBP+004450B5]
0177:006810AB C1F902 SAR ECX,02
0177:006810AE F3A5 REPZ MOVSB
0177:006810B0 8BC8 MOV ECX,EAX
0177:006810B2 83E103 AND ECX,03
Ré-adressage:
0177:006810A5 8BB5B5504400 MOV ESI,[EBP+004450B5]
0177:006810AB E9C00E0000 JMP 00681F70 > ici
0177:006810B0 8BC8 MOV ECX,EAX
0177:006810B2 83E103 AND ECX,03
Dans ce cas précis, il faudra que le premier (et éventuellement seul
patch) réécrive les SAR ECX,02 REPZ MOVSB. Après que les modifications voulues aient été
faites, vous redirigerez vers l'adresse 006810B0 pour que l'application continue, comme si de rien n'était.
Enjoy!
|