IntroHello, 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 = 00003A00Number 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: E2000060Program 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 00406B97Notre 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, 00409040Nous 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 ecxNous 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 retIl 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 retEnsuite 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 retIl 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 alOn 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 retMais 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 : 000272B8Maintenant, à 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, readableNous 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.
- L'agrandissement d'une section
- Rajouter 0x50 bytes à la fin du fichier à l'aide de HWS
- Modifier les infos de .madmat pour qu'elle prenne en compte les bytes rajoutés ( modification de RawSize et SizeOfImage )
Maintenant que nous avons notre section rajoutée, nous allons y inscrire le patch et dévier le programme vers ce patch.
- Rajout d'une section
- Restaurer la valeur de RawSize à sa valeur initial.
- Rajouter les bytes nécessaire pour aligner la section ( le RawSize d'une section doit TOUJOURS être un multiple du Code Offset )
- Ecriture du SectionHeader de notre section.
- Modification de RawSize de .madmat ( Pas obligatoire ), de SizeOfImage et de NumberOfSection.
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 004055C0Dernier 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 )