Intro

Hello, ceci vient comme suite direct de mon essai sur les modifications de section. J'ai choisis de cracker Apis32 de Vitaly Evseenko. Ce programme permet d'espionner les appels aux apis Windows, mais son utilité nous importe peu ici, bien qu'il soit peut-être très intéressant. Je ne sais pas par quoi ce programme est compressé, il contient une dernière section nommée .madmat, peut-être est-ce le nom de la compression ? N'empèche que cette compression ne nous laisse que 10 bytes de 00 à la fin de sa section. Il va donc falloire rajouter un peu de code :). Evidemment, il est tout à fait possible de se passer de rajouter du code, et de modifier du code non utilisé ( l'icône ou encore le PE Header ), mais mon but est, je le dis, de ré-expliquer l'agrandissement et le rajout d'une section dans un exemple pratique !

Première étape : Recherche des informations

Pour commencer le crack d'un programme compresser, nous devons toujours d'abord reguarder aux sections dont voici les infos :

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

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

   Object01: .text        RVA: 00001000 RawOffset: 00000400 RawSize: 00003A00 Flags: E0000060
   Object02: .idata       RVA: 00010000 RawOffset: 00003E00 RawSize: 00000C00 Flags: C0000040
   Object03: .rsrc        RVA: 00011000 RawOffset: 00004A00 RawSize: 00008A00 Flags: 40000040
   Object04: .madmat  RVA: 0001A000 RawOffset: 0000D400 RawSize: 0000161C Flags: E2000060

Program Entry Point = 0041A000 (apis32.exe File Offset:0000D400)

Nous voyons donc bien la section .matmad dont je parlais tout à l'heure L'EntryPoint ( EP ) correspond au premier offset de cette section. C'est donc le code contenu dans cette section qui se chargera de décompresser le code. Les procédures de décompression sont souvent linéaires, en d'autre terme, la dernière instruction dans le fichier sera pratiquement la dernière instruction exécutée. Donc à l'aide de WDasm je désassemble le fichier Apis32.exe et je vois :

:0041B5D2 E9E99FFEFF              jmp 004055C0
:0041B5D7 E9BBB5FEFF              jmp 00406B97

Notre Original EntryPoint ( OEP ) est surement l'une de ces 2 adresses et vous pourrez facilement vérifier que l'OEP vaut 004055C0 en tracant à l'aide d'un débugger ( Softice, TRW, TurboDebugger , etc... ). Reguardons à présent la place qu'il nous reste pour insérer notre patch à la fin du fichier :

:0041B615 44                      inc esp
:0041B616 00000000000000000000    BYTE 10 DUP(0)

Autant dire pas grand chose. 10 Bytes, avec ca nous pourrons pas modifier beacoup de code.

Deuxième étape : Recherche des bytes à modifier dans le programme original

Il est donc très simple ici de faire un Dump, je rappel le principe en 2 mots :

o Lancer ProcDump
o Lancer Apis32.exe avec le SymbolLoader ( si vous utilisez Softice )
o Mettre un Breakpoint sur l'addresse qui saute vers l'OEP ( ici : 0041B5D2 )
o Lorsque vous breakez, remplacer l'instruction par JMP EIP ( sous SoftIce et Trw cela se fait à l'aide de la commande A )
o Laisser tourner le programme et retourner dans ProcDump
        o Faire Refresh List
        o Dump Full ( à l'aide du bouton droit sur le Process correspondant à notre programme )
        o PE Editor pour modifier l'EP du fichier Dumper par l'OEP ( ici : 004055C0 )

Vous pourrez retrouver toutes les étapes en plus détaillées dans d'autres cours existants. N'oubliez pas que la valeur de l'EP dans le PE Header doit être écrite soustraite de l'image Base. Donc l'OEP ne vaut pas 004055C0 mais bien 000055C0 ( 004055C0 étant le Relative Virtual Addresse ( RVA ) de l'OEP ).

Une fois le fichier Dumpé, vous pouvez le désassembler à l'aide soit de IDA soit de WDasm. J'utilise pour ma part WDasm 8.93.
Passons maintenant au crack, facon standart. Grace aux String Refs, on trouve facilement ceci :

:004015CC E8CF300000              call 004046A0
:004015D1 EB01                         jmp 004015D4
:004015D3 B8                             BYTE B8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004015D1(U)
|
:004015D4 0AC0                        or al, al
:004015D6 7402                         je 004015DA
:004015D8 EB35                        jmp 0040160F

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004015D6(C)
|

* Possible StringData Ref from Data Obj ->"UNREGISTERED"
                                  |
:004015DA BFC8904000             mov edi, 004090C8

---- CUT ----

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004015D8(U)
|

* Possible StringData Ref from Data Obj ->"Registered to "
                                  |
:0040160F BF40904000              mov edi, 00409040

Nous avons donc en :004015D6 le jump conditionnel qui est conditionné par le CALL 004046A0. Donc, si AL vaut 0, il y a branchement du Jump. Le CALL devra dès lors être modifié pour renvoyer à chaque fois 1 dans AL. Reguardons le code de cette Fonction :

* Referenced by a CALL at Addresses:
|:004015CC   , :0040176A   , :00401D1A   , :00401F39   , :004025E6
|
:004046A0 51                      push ecx

Nous pouvons déjà constaté que cette procédure est appelée par 5 endroits différents.

* Possible StringData Ref from Data Obj ->"UserKey"
                                  |
:004046AC 6808964000             push 00409608
:004046B1 E81A030000            call 004049D0
:004046B6 83C40C                   add esp, 0000000C
:004046B9 83F810                    cmp eax, 00000010
:004046BC 7D08                       jge 004046C6
:004046BE 33C0                       xor eax, eax
:004046C0 5F                           pop edi
:004046C1 5E                           pop esi
:004046C2 5D                           pop ebp
:004046C3 5B                           pop ebx
:004046C4 59                           pop ecx
:004046C5 C3                          ret

Il vérifie que la longueur de la UserKey est plus grand que 10

:004046C6 6A2F                    push 0000002F
:004046C8 6800CF4000              push 0040CF00

* Possible StringData Ref from Data Obj ->"UserName"
                                  |
:004046CD 68F8954000              push 004095F8
:004046D2 E8F9020000              call 004049D0
:004046D7 83C40C                    add esp, 0000000C
:004046DA 83F805                    cmp eax, 00000005
:004046DD 7D08                        jge 004046E7
:004046DF 33C0                        xor eax, eax
:004046E1 5F                            pop edi
:004046E2 5E                           pop esi
:004046E3 5D                           pop ebp
:004046E4 5B                           pop ebx
:004046E5 59                            pop ecx
:004046E6 C3                           ret

Ensuite que le UserName est plus grand que 5.
Il y a juste après, si les 2 conditions sont vérifiées, le calcul du Serial dont voici la dernière partie :

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404807(U)
|
:00404815 03EA                    add ebp, edx
:00404817 41                         inc ecx
:00404818 4E                        dec esi
:00404819 75DA                    jne 004047F5
:0040481B 33C0                    xor eax, eax
:0040481D 5F                        pop edi
:0040481E 85ED                   test ebp, ebp
:00404820 5E                       pop esi
:00404821 5D                       pop ebp
:00404822 0F94C0               sete al
:00404825 5B                      pop ebx
:00404826 59                       pop ecx
:00404827 C3                      ret

Il y a donc une remise à 0 de eax ( XOR eax,eax ) suivi d'un SETE al conditionné par le TEST ebp, ebp.

Pour cracker une telle protection, qui se fie à la valeur renvoyée dans eax par la fonction de vérification du serial, il y a plusieur manière. La première serait de modifier les Jumps conditionnels suivant ainsi que le SETE :

:004046BC 7D08                       jge 004046C6
:004046DD 7D08                       jge 004046E7
:00404822 0F94C0                    sete al

On modifierait les JGE en JMP et le SETE en INC eax. Mais on peut aussi remplacer la première instruction de la fonction ( en :004046A0 ) par un

:004046A0  33C0                         xor       eax,eax
:004046A2  40                             inc       eax
:004046A3  C3                             ret

Mais cela ne fonctionne pas toujours, et vu que 'qui ne risque rien n'a rien', essayons cette méthode en patchant en mémoire à l'aide de Softice par exemple. Et cela fonctionne parfaitement dans notre cas. C'est une bonne nouvelle, car nous aurons donc qu'un DWORD à remplacer en mémoire pour patcher le code ! L'instruction qui fera le patch ressemblera à :

mov dword ptr[004046A0], C340C033

Maintenant que nous possèdons toutes les informations nécessaires à l'élaboration de notre patch, nous allons rajouter un peu de code vierge à notre programme et principalement à la dernière section .matmad.

Troisième étape : Agrandir la dernière section

Rappelons les infos concernant cette dernière section :

   Object04: .madmat  RVA: 0001A000 RawOffset: 0000D400 RawSize: 0000161C Flags: E2000060 VSize : 0000D268

A l'aide de ProcDump, ouvrez le fichier avec le PE Editor et rajouter 0x50 Bytes à la valeur de RawSize ( PSize ),de VSize et de SizeOfImage :

Les valeurs actuelles sont         : VSize : 0000D268 | PSize : 0000161C | SizeOfImage : 00027268
Les valeurs après modifications : VSize : 0000D2B8 | PSize : 0000166C | SizeOfImage : 000272B8

Maintenant, à l'aide de HexWorkShop, ouvrez le fichier Apis32.exe, situé votre curseur à la fin du fichier et à l'aide du menu qui apparait en clickant du bouton droit de votre souris, sélectionnez Insert... Mettez 50 Bytes et cochez Hex. Ok. Sauvez et relancez Apis32.exe pour voir si tout fonctionne.

Voilà, tout fonctionne, mais rajouter de l'espace à la dernière section est une chose très risquée ! Explications :

Si vous reguarder la VirtualSize de la section, elle vaut D268 et la RawSize : 161C. Or, la VirtualSize, c'est la memoire qui sera allouée à la section lorsqu'elle sera chargée en mémoire, et la RawSize, c'est la place que la section prend sur disque. La place allouée en mémoire est donc nettement plus grande que la place que la section prend sur disque, et lorsque le programme va être chargé en mémoire, il utilisera surement les D268 bytes de la section. Nos 50 bytes que nous venons de rajouter vont dès lors être écrasés. La solution serait que les 50 Bytes se trouve, en mémoire, après les D268 bytes de la section .madmat. Nous allons donc créer une section qui aura les carctéristiques nécessaire pour cela !

Rem : Je vous conseille de comparer la valeur de VSize et RawSize avant d'augmenter la grandeur de la section, si VSize est le plus grand, cela vous évitera du travail inutile. Moi, ici, je l'ai volontairement remarqué après l'agrandissement de la section, afin de ré-expliquer la méthode.

Quatrième étape : Rajout d'une section

Tout d'abord, il faut restaurer la VSize et la RawSize de la section .madmat à leur valeur d'origine. On retourne à une section dans cet état ci  :

   Object04: .madmat  RVA: 0001A000 RawOffset: 0000D400 RawSize: 0000161C Flags: E2000060 VSize : 0000D268

Les 50 bytes sont déjà rajouter, mais il va peut-être falloir en rajouter pour respecter l'alignement. Je vous rappels que le RawOffset de chaque section doit être un multiple du Code Offset ( ici : Code Offset = 00000400 ). Le RawOffset de .madmat est 0xD400 et sa RawSize = 0x161C. On somme les 2 valeur ce qui donne 0xEA1C. Cette valeur n'étant pas un multiple de 0x400, il va falloir trouver le plus proche supérieur à 0xEA1C qui est un multiple de 0x400. Cette valeur est 0xEC00 qui vaut 0x39 fois 0x400. Il va maintenant falloir rajouter ( EC00-EA1C ) bytes à la fin du fichier. Nous aurons donc 0x1E4 bytes pour aligner la section et 0x50 bytes ( déjà rajoutés ) en fin du fichier qui seront ceux de notre nouvelle section. Pour rajouter les 0x1E4 bytes, suivez les instructions décrites à la troisième étape.

Maintenant, il va falloir modifier le PE Header et rajouter notre Section Header. Commencons par le Section Header. Rassemblons les différentes infos de notre section que nous connaissons déjà :

    Name                          2E 54 65 65 4A 69 00 00   ; ".TeeJi"
    VirtualSize                   50 00 00 00                      ; VirtualSize = RawSize = 0x50
    VirtualAddress             ?? ?? ?? ??                      ; ?
    SizeOfRawData            50 00 00 00                      ; VSize = RawSize = 50
    PointerToRawData        00 EC 00 00                      ; RawOffset = 0x0000EC00
    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

Nous devons déterminer la valeur de VirtualAddress, soit de l'addresse de début de notre section lorsqu'elle sera chargée en mémoire. La section .madmat va s'étendre en mémoire ( sans prendre en compte l'ImageBase ) de 0x0001A000 ( RVA ) jusqu'à 0x00027268 ( RVA + VSize ). Le VirtualOffset à la même contrainte que le RawOffset, il doit être un multiple non pas du Code Offset mais de Section Alignement ( ici 0x1000 ), qui est une valeur écrite dans le Optional Header. Nous devons donc trouver le plus proche supérieur à 0x00027268 multiple de 0x1000, soit 0x28000.

    VirtualAddress             00 74 02 00                      ; 0x28000

Maintenant vous avez tout ce qu'il vous faut pour écrire notre Section Header dans le fichier. Après modifications, il ressemblera à ceci :

 00000170:  00 00 00 00-00 00 00 00-2E 74 65 78-74 00 00 00          .text
 00000180:  00 F0 00 00-00 10 00 00-00 3A 00 00-00 04 00 00   ­      :   
 00000190:  00 00 00 00-00 00 00 00-00 00 00 00-60 00 00 E0              `  Ó
 000001A0:  2E 69 64 61-74 61 00 00-00 10 00 00-00 00 01 00  .idata       
 000001B0:  00 0C 00 00-00 3E 00 00-00 00 00 00-00 00 00 00      >
 000001C0:  00 00 00 00-40 00 00 C0-2E 72 73 72-63 00 00 00      @  +.rsrc
 000001D0:  00 90 00 00-00 10 01 00-00 8A 00 00-00 4A 00 00   É     è   J
 000001E0:  00 00 00 00-00 00 00 00-00 00 00 00-40 00 00 40              @  @
 000001F0:  2E 6D 61 64-6D 61 74 00-68 D2 00 00-00 A0 01 00  .madmat hÊ   á
 00000200:  1C 16 00 00-00 D4 00 00-00 00 00 00-00 00 00 00     È
 00000210:  00 00 00 00-60 00 00 E2-2E 54 65 65-4A 69 00 00      `  Ô.TeeJi     ( Notre nouvelle section )
 00000220: 50 00 00 00-00 80 02 00-50 00 00 00-00 EC 00 00  P    t P    ý
 00000230: 00 00 00 00-00 00 00 00-00 00 00 00-20 00 00 60                 `

Je rappels que les données à modifier dans le PE Header sont NumberOfSection et SizeOfImage. La première valeur se trouve à l'offset 0x86 où vous modifiez 04 en 05 ( 5 Sections ).

 00000080:  50 45 00 00-4C 01 05 00-8C 26 69 37-00 00 00 00  PE  L î&i7  ( NumberOfSection )

A l'aide de procdump, éditez Apis32.exe, rajoutez 1E4 à la valeur de Size Of Image ( les 50 bytes de notre section ayant été rajoutés précédemment dans la Troisième étape ), rajoutez aussi 1E4 à la valeur de RawSize de .madmat. Sauvez tout, et relancez Apis32.exe pour voir si il fonctionne toujours ce qui, normallement, devrait être le cas.

J'aimerais résumer les différentes sous-étapes expliquées ci-dessus.

Après avoir fait cela, nous avons compris que notre code risquait de se faire écraser en mémoire. Il a donc fallu rajouter une section qui permetterait de mapper notre code après toute la place prise en mémoire par .madmat. Maintenant que nous avons notre section rajoutée, nous allons y inscrire le patch et dévier le programme vers ce patch.

Dernière étape : Ecriture du patch

La première chose à faire est de dévier le code vers notre patch, nous allons le dévier lorsqu'il veut redonner la main au programme ( le JMP OEP ). Mais encore faut-il savoir l'addresse de notre patch. Sur disque, l'espace que nous venons de rajouter se trouve entre l'offset EC00 et EC50 ( RawOffset + RawSize ). Pour connaitre les addresses en mémoire il suffit de rajouter à l'ImageBase ( ici : 0x400000 ) le RVA de la section ( ici : 0x28000 ) ! Donc en mémoire, notre section fraichement rajoutée s'étend de 0x428000 ( ImageBase + RVA ) à 0x428050 ( ImageBase + RVA + VSize ) !

:0041B5D2 E9E99FFEFF              jmp 004055C0

va devenir

:0041B5D2 E929CA0000               jmp 00428000

Rem : pour connaître l'équivalent hexa des instructions Asm, il suffit d'assembler sous Sice à l'aide de la commande A.

Maintenant nous insérons le patch en lui-même sans oublier de rajouter à la fin de celui-ci un JMP vers l'EOP.

:00428000 C705A046400033C040C3         mov      dword ptr[0004046A0], C340C033
:0042800A E9B1D5FDFF                          jmp       004055C0

Dernier mot

Nous avons à présent Apis32.exe cracké, avec une section en plus qui contient notre patch. J'espère que vous avez bien tout compris et que j'ai eu l'occasion de vous apprendre quelque chose. Y en a qui dirons surement : 'Ouais, c'est cool de rajouter des sections et tout le toutime, mais comment faire un patch.exe, petit et facilement ditribuable sur internet, qui fera cela, car moi je ne possède pas de patchMaker qui permet de faire de tels patchs :( '. Et bien moi je leur réponds que ce serait surement un beau sujet de cours, donc, à vos claviers ! :)

Amicalement,
TeeJi ( teejee@hotmail.com )