Les Programmes Packed - Encrypted

Janvier 2000
Béta 1.0

Avant Propos:

Le texte qui suit n'a pour objectif que de permettre une approche des différents packers/crypteurs. Il serait souhaitable que vous me fassiez remonter vos critiques et/ou suggestions afin de corriger et compléter cet essay, dans l'espoir qu'il puisse un jour être suffisamment complet pour devenir un texte intéressant…

Pour compléter et documenter cet essai, j'ai "emprunté" des extraits de textes aux Maîtres en cracking suivants:
CyberBob Jr , Nody, Neutral AtomX, Pulsar , Psyché, TaMaMBoLo, TeeJi
Que je remercie chaleureusement pour nous avoir montré la voie, souvent de façon magistrale, et que je voudrais assurer de mon plus profond respect!

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:
EXEPACK (EXE File)
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:

VG CRYPT

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!


Merci encore à CyberBob Jr , Nody, Neutral AtomX, Pulsar , Psyché, TaMaMBoLo, TeeJi, et à tous les autres…

Bonne Journée
Christal