Préface

    Aujourd'hui, je vais étudier avec vous le système WWPack32 de Piotr Warezak et Rafal Wierzbicki.
    Qu'est ce que WWPack32 ? La réponsese trouve dans la DOC officielle :
 

 

Registered version advantages

  • DLL compression,
  • full version EXEINFO-like tool which is capable of predicting compression ratios,
  • file signature option (only 'Shareware Author'/'Software Developer' version),
  • printed user's license available on request (only 'Shareware Author'/'Software Developer' version),
  • free 1.xx e-mail upgrades, WWPack32 distribution list subscription,
  • WWPack32 web support pages" access (http://xyz.xyz.xyz/xyz),
  • no shareware nag screen J

 
Overview

WWPack32 is an advanced Win32 executable file compressor. WWPack32 makes Windows 95/98/NT EXE and DLL files smaller and protects programs from cracking. Compressed files run exactly as before!

WWPack32 will save you disk space and it will also protect your programs from cracking.

Main features
 

  • The tightest compression ratio available: algorithms are based on the well known WWPACK for DOS, the winner of many executable compressors' comparisions.
  • Easy to use menu and quick menu.
  • Automatic file structure analyzer which searches for Windows executable files.
  • Full long filenames support.
  • 32bit decompression routine written in pure assembler which results in decompression speeds of over 1MB/sec (!).
  • Built in program code and relocation table compressor.
  • Full Windows 95, Windows 95 OSR2, Windows 98 and Windows NT compatibility.
  • Easy installation and configuration.
  • Optional compression levels.
  • Compatibility with any File Allocation Table.

Maintenant ce que la doc ne dis pas, c'est que ce programme ne peut pas TOUT compressé, ( j'aimerais savoir pourquoi !? si quelqu'un a une réponse. teejee@hotmail.com ). De plus, il ne peut faire qu'une couche de compression, et ne peut compressé que les fichiers qui n'ont jamais été compressé par un autres Compresseur / Crypteur.

Outils Utilisés

    Pour mon étude, j'ai utilisé

  • WWpack32 1.12 Beta Demo
  • Notepad.exe et Calc.exe fournis en standard avec Windows. ( Version fournie avec Windows 95 OSR 2. )
  • ProcDump v1.5 build 0
  • Hview 6.15
  • WDasm32 8.93
  • Softice 4.0
  • et Windows ( vous allez bientôt comprendre pourquoi je dis ca )

Première approche et unique approche.

    Ma première approche fut de comparer les PE Header ( et donc les différentes sections ) avant et après avoir compressé avec WWPack32. Je compresse donc une copie ( comme cela je garde toujours une version PROPRE de Notepad.exe ) de Notepad.exe et voilà les différentes information que WDasm nous fourni :

 
Disassembly of File: Notepad.exe
Code Offset = 00000400, Code Size = 00003A00
Data Offset = 00003E00, Data Size = 00000400

Number of Objects = 0006 (dec), Imagebase = 00400000h

   Object01: .text    RVA: 00001000 Offset: 00000400 Size: 00003A00 Flags: 60000020
   Object02: .bss     RVA: 00005000 Offset: 00000000 Size: 00000000 Flags: C0000080
   Object03: .data    RVA: 00006000 Offset: 00003E00 Size: 00000400 Flags: C0000040
   Object04: .idata   RVA: 00007000 Offset: 00004200 Size: 00000E00 Flags: 40000040
   Object05: .rsrc    RVA: 00008000 Offset: 00005000 Size: 00003200 Flags: 40000040
   Object06: .reloc   RVA: 0000C000 Offset: 00008200 Size: 00000A00 Flags: 42000040


Et voici maintenant les détail après le passage de WWpack32 :
 

Disassembly of File: notepad.exe
Code Offset = 00000400, Code Size = 00002200
Data Offset = 00002600, Data Size = 00000200

Number of Objects = 0007 (dec), Imagebase = 00400000h

   Object01: .text    RVA: 00001000 Offset: 00000400 Size: 00002200 Flags: E0000020
   Object02: .bss     RVA: 00005000 Offset: 00000000 Size: 00000000 Flags: C0000080
   Object03: .data    RVA: 00006000 Offset: 00002600 Size: 00000200 Flags: E0000020
   Object04: .idata   RVA: 00007000 Offset: 00002800 Size: 00000E00 Flags: C0000040
   Object05: .rsrc    RVA: 00008000 Offset: 00003600 Size: 00003200 Flags: C0000040
   Object06: .reloc   RVA: 0000C000 Offset: 00006800 Size: 00000200 Flags: C2000040
   Object07: .WWP32   RVA: 0000D000 Offset: 00006A00 Size: 00000800 Flags: C0000040

    De manière à éclaircir les chose et à bien voir ce qui à changé, je vais remetre ces information dans un tableau comparatif et rajouter quelques détails sur les sections ( comme la Virtual Size ) qui ne sont pas affichée dans WDasm. Pour récupérer les détails supplémentaires, j'ai utilisé ProcDump disponible sur procdump32.cjb.net
 
 
 

Virtual Size

Virtual Offset

Raw Size

Raw Offset

Caractéristiques

.text

00003953
00003A10

00001000
00001000

00003A00
00002200

00000400
00000400

60000020
E0000020

.bss

0000043A
0000043A

00005000
00005000

00000000
00000000

00000000
00000000

C0000080
C0000080

.data

00000212
00000410

00006000
00006000

00000400
00000200

00003E00
00002600

C0000040
E0000020

.idata

00000C9A
00000C9A

00007000
00007000

00000E00
00000E00

00004200
00002800

40000040
C0000040

.rsrc

00004000
00004000

00008000
00008000

00003200
00003200

00005000
00003600

40000040
C0000040

.reloc

0000091E
0000091E

0000C000
0000C000

00000A00
00000800

00008200
00006800

42000040
C2000040

.WWP32

-
000006B7

-
0000D000

-
00000800

-
00006A00

-
C0000040

    * La première valeur est la valeur avant compression, la 2ème est donc après compression.
    * Les valeur en rouge sont les valeur qui ont été modifiées.
 
 
 

Avant compression

Après compression

Entry Point

00001000

0000D000

Image Base

00400000

00400000

   Voilà donc un premier aperçu des changement aparus dans le PE Header. et donc dans les section. On peut déjà voir plusieurs point important :

  • Aparission d'une nouvelle section appelée .WWP32 et contenant le Loader qui va décompresser le programme.
  • Réduction de la taille sur disque ( Raw Size ) des sections .text, .data et .reloc.
  • Modification des Flags ( caractéristiques ) de toutes les sections.
  • Changement de l'EntryPoint qui pointe maintenant sur 0000D000 qui est en fait la première instruction de la section .WWP32.
  • La taille des section en mémoire est toujours identique, ce qui explique que le code se décompresse sur lui-même.

    Après cela, je crois qu'il serait intéressant de jeter un oeil aux dead-listing ( code désassemblé ) du fichier compressé. Mais il y a un problème car quand on veut se rendre à l'adresse de l'Entry Point du programme Compressé ( EPC ), on se rend compte qu'il n'est pas désassemblé. En effet, nous voyons le code des section .texte et .data. En fait, cela ce passe a cause des Flags des section. Si vous les regardez, vous verrez que il y a uniquement .text et .data qui ont les Flags E0000020. Ca veut dire, pour le programme, que ces sections contiennent du code exécutable. Les autres sections n'ayant pas ces caractéristiques, elles ne seront donc pas désassemblées.

    La réponse à ce problème va être rapide, nous allons simplement changer les flags de la section .WWP32 en E0000020. Pour ce faire, il faut retourner dans ProcDump, clicker sur PE-Editor, sélectionner le fichier compressé, clicker sur Section puis clicker avec le bouton droit sur la section .WWP32 et mettre dans la partie caractéristique E0000020 et pour que tout fonctionne bien, il faut aussi mettre la section .data à C0000040. Voici mes sections après modifications :

   Object01: .text    RVA: 00001000 Offset: 00000400 Size: 00002200 Flags: E0000020
   Object02: .bss     RVA: 00005000 Offset: 00000000 Size: 00000000 Flags: C0000080
   Object03: .data    RVA: 00006000 Offset: 00002600 Size: 00000200 Flags: C0000040
   Object04: .idata   RVA: 00007000 Offset: 00002800 Size: 00000E00 Flags: C0000040
   Object05: .rsrc    RVA: 00008000 Offset: 00003600 Size: 00003200 Flags: C0000040
   Object06: .reloc   RVA: 0000C000 Offset: 00006800 Size: 00000200 Flags: C2000040
   Object07: .WWP32   RVA: 0000D000 Offset: 00006A00 Size: 00000800 Flags: E0000020

    Maintenant si vous désassemblez à nouveau le fichier compressé, vous aurez le dead-listing de la section WWP32 et donc du Loader. On jette un oeil et on repère des endroit ou insérer plus tard notre patch :
 

:0040D6B3 00000000000000000000    BYTE 10 DUP(0)
...
:0040D7FD 00000000000000000000    BYTE 10 DUP(0)

    Ben voilà un bon paquet de place où nous allons pouvoir insérer notre code. Mais, il y a un problème. Si vous avez regardé tout à l'heure la place allouée en mémoire ( Virtual Size ) pour la section .WWP32, vu avez pu remarquez qu'elle n'est que de 000006B7. Donc en mémoire, la section ira de (00400000+0000D000) à (00400000+0000D000+000006B7) soit de 0040D000 à 0040D6B7 !! Donc, il va falloire modifier cela pour qu'elle aille jusqu'à, par exemple, 0040D800 ( 800 bytes est le maximum que nous pourrons utiliser car c'est le nombre de bytes qui se trouve sur le disque. !! le calcul à faire pour le connaître ce maximum est ImageBase + Virtual Offset + Raw Size ). Nous aurons donc 0040D800-0040D6B3=14D hexa soit 333 bytes à notre disposition :).

    Je jette vite fait un coup d'oeil sur le dead-listing du Loader, et je tombe ici ( est-ce que les dieux étaient avec moi ? :) :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D26D(C)
|
:0040D281 58                      pop eax
:0040D282 61                      popad
:0040D283 58                      pop eax
:0040D284 8BE8                    mov ebp, eax
:0040D286 2E038595020000          add eax, dword ptr cs:[ebp+00000295]
:0040D28D 0599020000              add eax, 00000299
:0040D292 5D                      pop ebp
:0040D293 5B                      pop ebx
:0040D294 E9673DFFFF              jmp 00401000


    Un jump qui saute en 00401000 ce qui est l'EP du notpad Original ( EPO )! Je sais pas ce que vous en pensé, mais ils auraient pu indiquer le chemin avec des flèches phosfluorescente s'était pareil ! :) Enfin.. pour vérifier qu'il n'y a qu'une et une seul référence à l'EP du programme original, je me rend au début de la section .text en 401000 et je vois :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D294(U)
|
:00401000 0A00                    or al, byte ptr [eax]

    Ben voilà une bonne nouvelle, je crois que j'ai trouvé l'adresse de l'instruction qui va sauter sur l'EPO quand celui-ci sera décompressé. Le plus marrant serait que l'adresse de l'instruction se trouve toujours à 0x294 bytes de l'EPC ( Entry Point du programme Compressé ). Mais on verra cela plus tard.

    Maintenant, reguardons un peu le début du code du loader à partir de l'EPC. Nous voyons ceci :

:0040D000 53                      push ebx
:0040D001 55                      push ebp
:0040D002 8BE8                    mov ebp, eax
:0040D004 33DB                    xor ebx, ebx
:0040D006 EB60                    jmp 0040D068
...
:0040D066 0D0AE80000              or eax, 0000E80A
:0040D06B 0000                    add byte ptr [eax], al
:0040D06D 58                      pop eax
:0040D06E 2D6D000000              sub eax, 0000006D
:0040D073 50                      push eax
:0040D074 60                      pushad
:0040D075 33C9                    xor ecx, ecx
:0040D077 50                      push eax

    Si vous regardez le jump en 0040D006, vous voyez qui ne saute pas sur une instruction affichée, mais il se décalle de 2 bytes sur la droite. Ceci à 2 buts( voir même plus ). Le premier étant de mettre la confusion dans le petit cerveau que hmm.. la vie à bien voulu nous donner, et deuxièmement, à bloquer certain debugger ( celui de WDasm notamment ).

    Je me suis donc amusé à réécrire le code 2 byte décalé et ca donne ceci. On voit qu'ici "l'équilibre" est vite revenu à la normal, mais imaginez que le programme fasse cela tout le temps. Je vous jure qu'alors cela devient impossible !
 

00000068: E800000000                   call      00000006D     |  ceci est une méthode pour récupérer
0000006D: 58                           pop       eax           |  EAX = l'EIP courant.
0000006E: 2D6D000000                   sub       eax,00000006D
00000073: 50                           push      eax
00000074: 60                           pushad
00000075: 33C9                         xor       ecx,ecx
00000077: 50                           push      eax

    Bon, je ne vais pas m'attarder plus longtemp sur le Loader car nous savons tout ce qu'il faut pour patcher un programme compressé avec WWPack32. Mais, si vous vous souvenez, j'avais dis qu'il fallait vérifier si l'instruction qui jump vers l'EPO est toujours à la même distance de l'EPC. Je vais donc compresser un autre programme ( Calc.exe fournit aussi avec Windows 95 OSR 2 ). Et voici ce que le PE Header m'apprend :

    Avant Compression :

Disassembly of File: Calc.exe
Code Offset = 00000400, Code Size = 00009800
Data Offset = 00009C00, Data Size = 00001800

Number of Objects = 0006 (dec), Imagebase = 00400000h

//******************** Program Entry Point ********
:0040534E 64A100000000            mov eax, dword ptr fs:[00000000]

    Après compression :

Disassembly of File: copie de calc.exe
Code Offset = 00000400, Code Size = 00005C00
Data Offset = 00006000, Data Size = 00000C00

Number of Objects = 0007 (dec), Imagebase = 00400000h

   Object07: .WWP32   RVA: 00013000 Offset: 00009200 Size: 00000C00 Flags: C0000040

//******************** Program Entry Point ********
:00413000 53                      push ebx

    On a donc bien une section supplémentaire nommée .WWP32 ajoutée dans le programme compressé et dont la première instruction est l'EPC. Maintenant, allons voir en 00413000+294 = 00413294 où on devriait trouver le jmp vers l'EPO :

:00413294 E9B520FFFF              jmp 0040534E  | 0040534E est bien l'EP du programme original :)

    Afin de vérifier tout ce que j'ai dit, j'ai fait un patch pour notepad qui enlève l'acces à l'aide. Ceci n'a rien d'extraordinaire, j'ai juste nopper le call WinHelpA. J'ai inséré mon patch en 0040D6B3 et modifié le jmp 401000 en 40D294 pour qu'il jump sur mon patch en 40D6B3 et j'ai rajouter un jmp 401000 à la fin de mon patch. Voici la preuve en image :
 

.0040D294: E91A040000                   jmp      .00040D6B3   | le jump va vers mon patch
...
.0040D6B3: C7052C12400090909090         mov       d,[00040122C],090909090   |  Le patch en question
.0040D6BD: 66C705301240009090           mov       w,[000401230],09090       |
.0040D6C6: E93539FFFF                   jmp      .000401000                 |  Le Jump vers L'EP

    J'ai ensuite modifié le Virtual Size de la section .WWP32 en lui mettant la valeur de son RAW Size. Ainsi je peut utiliser tout le code ( les 000000 ) situé à la fin de la section et mon patch est chargé en mémoire. Je lance mon notepad modifié et là, erreur. Je reguarde ce que Windows me dis :

NOTEPAD a causé une défaillance de page dans
le module NOTEPAD.EXE à 0147:0040d271.
Registres :
EAX=00000000 CS=0147 EIP=0040d271 EFLGS=00000202
EBX=0040d6b7 SS=014f ESP=0063fe0c EBP=0040d000
ECX=00400000 DS=014f ESI=126c05c7 FS=18d7
EDX=00000000 ES=014f EDI=00406410 GS=0000
Octets à CS : EIP :
01 06 33 d2 8a 13 43 80 fa 00 74 e8 03 f2 eb f0
Etat de la pile :
0040d000 81744c08 81744904 0040d000 0063fe30 00000000 81744964 81744924 0040d000 0040d000 0063ff78 00530000 bff88e93 81744c08 81744904 00530000

    Une défaillance en 0040D271, cela ressemble plus à une protection qu'autre chose car cette addresse fait partie du code du Loader ( car elle appartient à la section .WWP32 ). Je me rend donc en 0040D271 et je trouve ceci :

.0040D271: 0106                         add       [esi],eax
.0040D273: 33D2                         xor       edx,edx
.0040D275: 8A13                         mov       dl,[ebx]
.0040D277: 43                           inc       ebx
.0040D278: 80FA00                       cmp       dl,000 ;" "
.0040D27B: 74E8                         je       .00040D265    | Si DL vaut 00 on sort de la boucle
.0040D27D: 03F2                         add       esi,edx
.0040D27F: EBF0                         jmps     .00040D271    | Boucle tant que DL <> de 00

allons voir ce qui se trouve en 0040D265 :

.0040D265: 8B33                         mov       esi,[ebx]
.0040D267: 83C304                       add       ebx,004
.0040D26A: 83FE00                       cmp       esi,000
.0040D26D: 7412                         je       .00040D281    | Jump si esi = 00
.0040D26F: 03F1                         add       esi,ecx      | sinon il recommence la boucle.
.0040D271: 0106                         add       [esi],eax

et en 0040D281 on a :

.0040D281: 58                           pop       eax
.0040D282: 61                           popad
.0040D283: 58                           pop       eax
.0040D284: 8BE8                         mov       ebp,eax
.0040D286: 2E038595020000               add       eax,cs:[ebp][000000295]
.0040D28D: 0599020000                   add       eax,000000299 ;"  Ö"
.0040D292: 5D                           pop       ebp
.0040D293: 5B                           pop       ebx
.0040D294: E91A040000                   jmp      .00040D6B3     | Jump vers notre patch
 

    A ce que j'ai compris, cette boucle sert en fait à vérifier à quelle addresse la section se termine ( et que le reste de la section est donc remplie avec des 00 ). Quand il trouve cette adresse, il lui soustrait l'adresse à laquelle elle devrait se terminer ( que lui connait ) et il vérifie si le résultat de cette soustraction vaut 00. Sinon, c'est que quelqu'un à rajouté du code à la fin d'une section pour, par exemple, rajouter un patch :) ( Infos à vérifier )

    La solution du problème est donc évidente. Il suffit de forcer les jumps conditionnels en 0040D27B et en 0040D26D. Nous aurons donc maintenant :
 

.0040D26D: EB12                         jmps     .00040D281   -------- (1)
...
.0040D27B: EBE8                         jmps     .00040D265   -------- (2)

    Voici donc comment résoudre la seule et unique protection, en utilisant non pas Softice, mais en utilisant les infos données par Windows lors d'une erreur :)

    Rem : Je force les 2 jumps conditionnels par mesure de sécurité.
 
 

Résumer et conclusions

    Résumons-nous maintenant :

  • Pour détecter que la compression est WWPack32, il suffit de regarder le nom de la dernière section qui sera normallement nommée .WWP32.
  • Pour insérer notre patch, nous avons généralement de la place en fin de la section .WWP32  mais il faut modifier la valeur de la VirtualSize de cette section par la valeur de son RawSize. Généralement vous aurez plusieurs centaines de bytes de libre de cette manière.
  • Pour faire que notre patch s'exécute, il faut modifier l'instruction en ImageBase + EntryPoint + 294 ( en hexa ) qui saute sur l'EPO. Vous modifier ce jump pour qu'il saute à l'adresse de notre patch et vous retapé le jump EPO à la fin de notre patch pour qu'il lance le programme original ! N'oubliez pas non plus de modifier les 2 jumps de la protection qui se trouve respectivement à 0x26D et 0x27B de l'EPC.

   Conclusions :

Amicalement,
TeeJi
teejee@hotmail.com