Protection |
ASProtect |
Ready Made Protection |
||
Outils |
SoftIce | |||
Cible |
Advanced Registry Tracer 1.1 b |
By Christal |
Auto-Psy d'une Ready Made Protection
Avant Propos
Depuis une quinzaine, le hasard veut que dès que je m'intéresse à un programme, celui ci soit protégé par la même Ready Made. Je n'ai pas encore identifié avec certutude cette joyeuseté, mais il s'agit très certainement d'ASProtect, le dernier bébé d'Alexey Solodovnik, et vous trouverez ci dessous tout ce qui m'a semblé nécessaire à mieux la connaître. La première fois que j'ai rencontré cette protection, pour Alchemy Laucher 1.0, l'essay que j'ai écrit dans la foulée laissait des zones d'ombres, et surtout n'indiquait pas comment j'avais pu trouver les différentes adresses m'ayant permis d'écrire un patch. Je vais y remédier dans le chapitre Step by Step…
Cette " étude " porte sur 5 programmes : Alchémy Laucher 1.0, le premier de la série, Icon Grabber 2.18, dont je vous engage à lire l'essay qu'a écrit Metallica, Cool Desk 99, Batch Converter, et Assembly Registry Tracer 1.1 b.
Reconnaître cette Ready Made Protection : les Symptômes
Debugger detected ! Si vous lancez FrogIce, vous apprendrez que cette Ready Made cherche à détecter
SoftIce pour les versions W98 et NT, par la méthode MetlIce (Code 0B), soit deux beaux écrans bleu
qui apparaîtront, pilotés par FrogIce. L'écran bleu de FrogIce va apparaître en traçant sur le Kernel32
! _lopen Dans le cas de cette protection, j'aurais tendance à y voir une variante
: Au retour de l'API, EAX est incrémenté, et s'il vaut 00, SICE est
détecté. Or si SI est présent, la valeur retournée sera -01 ! Et pour le second : détection SICE A la fin du fichier exécutable, un éditeur hexadécimal vous
permet de voir :
Pour finir, une tentative de désassemblage ne vous donnera aucun listing,
mais vous obtiendrez quelques informations sur le PE Header, et l'Entry Point de l'application :
Une modification des attributs de la première section du PeHeader, couplé
avec la présence active de FrogIce, va être détectée et le programme balancera une nouvelle
MessageBox " File corrupted ". Pour les cinq programmes étudiés, la section .rsrc est en doublon,
et pour quatre d'entre eux, la section .aspr est présente. Dans le cas d'Advanced Registry Tracer 1.1b cette
section est appelée .tls Mais dans tous les cas, l'Entry Point de l'application se situe dans l'avant dernière
section (.aspr ou .tls suivant le cas), avec une Image Base à 00400000. Comme il s'agit d'une Ready Made, vous retrouverez toujours ces mêmes codes
pour les 5 programmes. Hop ! VU ! De nouveau une simulation de retour sur call appelant : Yep ! Une fois de plus on trouve un Push Eax/Ret, et c'est dans cette routine que les
informations nécessaires à l'affichage de la MessageBox vont être initialisées, avant
de renvoyer vers un Exit Process. Il va de soit qu'à chaque fois que vous rencontrez un Push Eax/Ret, vous
relevez la valeur de EAX. Dans ce cas précis, cette valeur renvoi vers une adresse 004XXXXX et vous avez
un POPAD (caractéristique !, on restaure les registres…) au dessus. Si vous continuez le traçage,
vous aurez un classique démarrage de programme, avec l'abandon du codage CCA. EAX contient donc l'OEP (Original
Entry Point), à EP2 - 0x34D5. - Lui ajouter 0x04DC pour pourvoir récupérer l'adresse de la seconde
partie du décryptage (EP2) poussé sur la pile Decompression advanced registry tracer 1.11. (courtesy Of SV) d 4cd000 et on vois une table d'import nickel :-) En 00C8F4B2 et 00C8F4BC, le programme ecrase la table et donc c'est ici qu'il faut
intervenir pour noper ces 2 instructions. Il ne reste plus ensuite qu'à se caller sur le Push eax/Ret pour
le remplacer par un jmp Eip et réaliser un dump avec ProcDump (ATTENTION : Option 'Don't Rebuild Import'
puisque on a laisse l'import tel quel) et le résultat obtenu sera xécutable! Une autre solution, proposée par el.CaRaCoL
est de Partir du principe que le loader d'Asprotect doit décompresser les données (qu'il a lui-même
compressé) et restituer la section idata telle qu'elle était à l'origine à un moment
donné ou un autre, il va bien être obligé d'écrire les bytes à leur emplacement
d'origine ! On trace par F10 jusqu'à l' exécution du 2ème movsb pour terminer
la copie de tous les bytes vers la section idata puis X ou F5 il suffit de tracer avec F8 pour arriver
ici :
Je ne détaille pas davantage, c'est du déjà vu. Sauvegarder le dump full sous " cible_upk.exe" puis kill du process. Advanced Registry Tracer 1.1 b Réalisation d'un patch Step by Step : Pour ce genre de question, la réponse est un BPM 00489D0C… Et ainsi de suite. 00C9C3DA est décompressé en 00C9C0E7, avec de nouveau
un Push 00008000 pas loin. Cette procédure est elle-même décompressée en 004F530F avec
un JMP 004F51F8 quelques lignes plus basses, et finalement cette dernière adresse est décompressée
en 004F50AC (et encore une fois avec un Push 00008000 à proximité) dans une zone clean, c'est à
dire non-compressée et accessible par un éditeur hexadécimal. OUF ! Puis je vérifie que l'adresse que je vise est bien décompressée
(par un d adresse_visée), et je trace le patch écrit précédemment, qui modifie un Push
00008000 en JMP 004F62AF (patch 2) Un BPM 004F5314 X va me permettre de m'assurer que tout va bien : Un d C9C0EF va permettre de se rendre compte que l'adresse du jump_vers_patch_3
n'est pas encore décompressée. Il y a donc plusieurs passages dans la routine ci dessus avant que
l'adresse convoitée ne soit disponible. Pour éviter un crash fatal, il va falloir tester la présence
en 00C90EF de l'octet-test qui nous garantira que nous pouvons procéder à la modification : Ici, il n'y a pas lieu de restaurer d'octets écrasés puisque j'ai
remplacé un jump par un autre. Celui qui clôture la fin du patch_2 est le même que celui qui
se trouvait en 00C90EF. et ainsi de suite pour chacun des différents patchs à écrire
:
Arrivé au patch 4, vous allez commencer par vous débarrasser du call
Sice_détecté, en remplaçant le 5A par un C3 (opcode pour Ret), restaurer les octets écrasés,
et préparer la saut_vers_patch_5 Si vous vous en rappelez, la modification que j'envisage de faire en 00C8F2E7 n'est
que de 5 octets (E9 21 70 86 FF), mais que je prends la place d'une combinaison de 6 octets. Pour éviter
de déséquilibrer les codes de l'application, j'ai rajouté 90 (opcode pour NOP) comme 6ème
octet. Et enfin, nous arrivons au patch qui va permettre de faire de nous des utilisateurs
enregistrés en remplaçant un 85 par un 33 Vous voici avec une version d'Advanced Registry Tracer agréablement relookée…
Au lancement de la protection vous avez une MessageBox qui surgit annonçant :
Please remove it before
start of application.
=> Art
** SOFTICE DETECTION ** code 0B, at cs:00C8F5F0
Attempting to load: NTICE
=> Art
** SOFTICE DETECTION ** code 0B, at cs:00C8F5FC
Attempting to load: SICE
Allons voir, Merci Frog's Print !:00C8F65E A12C34C900 MOV EAX,[00C9342C]
:00C8F663 50 PUSH EAX
:00C8F664 E8734DFFFF CALL KERNEL32!_lopen > ici
:00C8F669 40 INC EAX
:00C8F66A 7510 JNZ 00C8F67C
:00C8F66C 6A00 PUSH 00
:00C8F66E A13034C900 MOV EAX,[00C93430]
:00C8F673 50 PUSH EAX
:00C8F674 E8634DFFFF CALL KERNEL32!_lopen > et ici
:00C8F679 40 INC EAX
:00C8F67A 7425 JZ 00C8F6A1
Habituellement un Code OB (MeltIce) est écrit ainsi :00401067: push 00402025 > \\.\SICE
0040106C: call CreateFileA
00401071: cmp eax,-001
00401074: je 00401091
00C8F65E MOV EAX,[00C9342C]
00C8F663 PUSH EAX
00C8F664 CALL KERNEL32!_lopen
00C8F669 INC EAX
00C8F66A JNZ 00C8F67C
Une petite interrogation de EAX avant l'appel à l'API va donner :
Pour le premier appel : détection NTICE:00C8F5EC 5C 5C 2E 5C 4E 54 49 43-45 00 00 00 5C 5C 2E 5C \\.\NTICE...\\.\
:00C8F5FC 53 49 43 45 00 00 00 00-55 8B EC 83 C4 E4 53 56 SICE....U.....SV
:00C8F5F8 5C 5C 2E 5C 53 49 43 45-00 00 00 00 55 8B EC 83 \\.\SICE....U...
:00C8F608 C4 E4 53 56 57 33 D2 89-55 E8 89 55 E4 33 D2 55 ..SVW3..U..U.3.U
0.......File cor
rupted !...=..De
bugger detected
!...n...$......_c
+++++++++++++++++++ ASSEMBLY C0DE LISTING ++++++++++++++++++
//********************** Start of Code in 0bject **************
Program Entry Point = 004F5000 (ART.exe Fi1e 0ffset:000F5000)
Heureusement, cette Ready Made se contente de vérifier l'intégrité des informations du PE
Header. Par la suite vous verrez qu'une modification des codes de l'exécutable sera sans conséquence.
La solution qui apparaît la meilleur est alors de placer un CC à la place du Pushad sur l'Entry Point
de l'application, et de provoquer un break par un BPINT 3.
Regardons un peu les caractéristiques des sections :
Alchemy Laucher 1.0.text 00023000 00001000 00012A00 00001000 C0000040
.rdata 00009000 00024000 00002A00 00013A00 C0000040
.data 00007000 0002D000 00000E00 00016400 C0000040
.rsrc 0000E000 00034000 00006200 00017200 C0000040
.aspr 00013000 00042000 00013000 0001D400 C0000040 > EP
.rsrc 00001000 00055000 00000000 00030400 C0000040
Advanced Registry Tracer 1.1bCODE 00094000 00001000 00040E00 00000400 C0000040
DATA 00003000 00095000 00001000 00041200 C0000040
BSS 00035000 00098000 00000000 00042200 C0000040
.idata 00003000 000CD000 00001000 00042200 C0000040
.tls 00001000 000D0000 00000000 00043200 C0000040
.rdata 00001000 000D1000 00000200 00043200 C0000040
.reloc 0000B000 000D2000 00000000 00043400 C0000040
.rsrc 00018000 000DD000 00005600 00043400 C0000040
.tls 00010000 000F5000 0000F200 00048A00 C0000040 > EP
.rsrc 00001000 00105000 00000000 00057C00 C0000040
Si vous modifiez les caractéristiques de la première section en remplaçant le C0000040 par
un E0000020 (l'explication des effets de ce changement se trouvant dans de nombreux essay's, je ne rentrerai pas
plus dans les détails), vous aurez un break dans SI.:0043F001 60 PUSHAD > Entry Point
:0043F002 E801000000 CALL 0043F008
:0043F007 90 NOP
:0043F008 5D POP EBP
:0043F009 81EDE3E54400 SUB EBP,0044E5E3
Si vous attaquer un traçage normal (F10), la MessageBox va surgir dès que vous passerez sur le call
0043F008. A la seconde tentative, un Step Into (F8) va vous permettre de découvrir les joies des Codes Changeant
d'Apparences (CCA ou codes polymorphes, ou encore OverLapping).
Dans ce type de codage, dont Pulsar en a donné d'excellentes explications, vous devez a priori tracer Step
Into tous les call 004XXXXX, et Step Over les call [EBP+00000XXX] qui sont des appels aux API.
Avec un peu de patience et beaucoup de temps, vous allez repérer, au cours du tracing, les différents
points de passage du crypteur/compresseur. Nous verrons au fur et à mesure que ces adresses sont toujours
placées à la même distance les unes des autres, quelques soit l'application concernée
:
Cool Desk 99 ::0043F4D1 E8C3EB019B CALL 9B45E099
:0043F4D6 58 POP EAX
:0043F4D7 EB01 JMP 0043F4DA
:0043F4D9 E8EB01E968 CALL 692CF6C9
:0043F4DD 6801E06D00 PUSH 006DE001
:0043F4E2 C3 RET
Un push adresse/ ret n'est ni plus ni moins qu'une simulation de retour sur Call appelant, et en l'occurrence celle
ci va faire passer l'application des adresses 004XXXXX à 006XXXXX.
Pour certains programmes, comme Alchemy, l'adresse poussée sera la même, mais dans le cas d'Icon Grabber
celle ci vaudra 00C8XXXX. 0043F4DD va donc être un point de passage obligé pour venir y relever la
valeur poussée sur la pile.
On notera donc :
EP + 0x04DC = 0043F001 + 0x04DC = 0043F4DD > passe la main aux adresses 6D ou C8.
Et le 4DC se vérifie pour les cinq programmes ciblés…
006DE001 va devenir mon EP2.
Continuons le tracing ::006DE5C5 61 POPAD
:006DE5C6 7508 JNZ 006DE5D0
:006DE5C8 B801000000 MOV EAX,00000001
:006DE5CD C20C00 RET 000C
:006DE5D0 68600B6D00 PUSH 006D0B60
:006DE5D5 C3 RET
EP2 + 0x05D4 = 006DE001 + 0x05D4
= 006DE5D5
Ce calcul n'est plus vrai que pour 4 programmes et il en sera ainsi jusqu'à la fin, ART n'est plus concerné
par cette base de calcul, par contre on retrouvera les mêmes codes à une longueur de saut différente.
C'est reparti ::006DE0FA 50 PUSH EAX
:006DE0FB FF9541294400 CALL [EBP+00442941]
:006DE101 8D85152C4400 LEA EAX,[EBP+00442C15]
:006DE107 50 PUSH EAX
:006DE108 C3 RET
Cette fois ci c'est un Push Eax/Ret.
Il se trouve à EP2 + 0x0106.
On continue :
En traçant sur un call 006CFFF0 en 006D058C, la MessageBox " Debugger Detected " va surgir.
Nous sommes à EP2 - 0xDA75.
Entrons dans ce call ::006CFFF0 55 PUSH EBP > à remplacer par un C3
:006CFFF1 8BEC MOV EBP,ESP
:006CFFF3 83C4F8 ADD ESP,-08
:006CFFF6 8955F8 MOV [EBP-08],EDX
:006CFFF9 8945FC MOV [EBP-04],EAX
:006CFFFC 81C4C8000000 ADD ESP,000000C8
:006D0002 B8EB436C00 MOV EAX,006C43EB
:006D0007 40 INC EAX
:006D0008 6AFF PUSH FF
:006D000A BB7D436C00 MOV EBX,006C437D
:006D000F 4B DEC EBX
:006D0010 6830200000 PUSH 00002030
:006D0015 FF75FC PUSH DWORD PTR [EBP-04]
:006D0018 FF75F8 PUSH DWORD PTR [EBP-08]
:006D001B 6A00 PUSH 00
:006D001D 53 PUSH EBX
:006D001E 50 PUSH EAX
:006D001F C3 RET
Suivant vos préférences, vous pouvez aussi bien ajouter 5 à l'EIP en EP2 + DA75, pour "
sauter " le call Sice_détecté, ou placer un Ret au tout début de la routine embarrassante.
Dernière ligne droite, le passage de relais de la Ready Made Protection au programme d'origine ::006D04C6 8BC0 MOV EAX,EAX
:006D04C8 89C4 MOV ESP,EAX
:006D04CA 89D0 MOV EAX,EDX
:006D04CC 8B1D34566D00 MOV EBX,[006D5634]
:006D04D2 89041C MOV [EBX+ESP],EAX
:006D04D5 61 POPAD
:006D04D6 50 PUSH EAX
:006D04D7 C3 RET
Avec ces informations, vous avez de quoi réaliser un Dump Memory de l'application, en remplaçant
le push eax/ret par un jmp EIP. Resteque le dump obtenu ne va pas être excutable. Nous verrons comment faire
dans un instant.
Mieux, j'estime disposer de suffisamment d'éléments pour envisager la réalisation d'un Unpacker,
sachant que celui ci devra :
- Relever l'adresse de l'Entry Point du programme crypté en cherchant dans les codes de l'exe les informations
sur celui ci qui sont situées de l'offset 128 à l'offset 12BAdvanced Back Converter Entry Point: 004DB001
00000100 5045 0000 4C01 0A00 195E 422A 0000 0000 PE..L....^B*....
00000110 0000 0000 E000 8E81 0B01 0219 0024 0A00 .............$..
00000120 0030 0300 0000 0000 01B0 0D00 0010 0000 .0..............
Quick Icon Grabber Entry Point: 004ED001
00000100 5045 0000 4C01 0A00 195E 422A 0000 0000 PE..L....^B*....
00000110 0000 0000 E000 8E81 0B01 0219 0092 0900 ................
00000120 00D6 0400 0000 0000 01D0 0E00 0010 0000 ................
Advanced Registry Tracer Entry Point: 004F5000
00000100 5045 0000 4C01 0A00 195E 422A 0000 0000 PE..L....^B*....
00000110 0000 0000 E000 8E81 0B01 0219 0038 0900 .............8..
00000120 0072 0200 0000 0000 0050 0F00 0010 0000 .r.......P......
- Retrancher 0xDA75 à EP2 pour arriver sur le call Sice_détecté, ou poser un bpx _lopen, puis
tracer pour trouver le call
- Ajouter 5 à l'EIP pour sauter ce call
- Retrancher 0x34D6 à EP2 pour relever la valeur de l'OEP dans EAX, ou rechercher la signature 89 04 1C
61 50 C3
- Réaliser un dump mémory de l'application (voir les indications ci dessous)
- Placer la nouvelle valeur de l'OEP
Mais c'est une autre histoire, et j'espère que j'aurais l'occasion de vous en reparler.
A ce propos, j'encourage les codeurs à se pencher sur cet exercice, en vue de la confrontation des techniques
utilisées, et qui pourraient être développées dans un prochain essay.
Eventuellement vous pouvez vous inspirer de l'UN-Peshield codé par Pulsar.
Le but est, encore une fois, d'avoir un dump correct de l'exe et donc d'avoir une section import valide, ce qui
n'est pas le cas pour le moment.
Voyons les infos contenues dans les sections de art.exe:
On a une section .idata en CD000 (virtual offset) et donc en 4CD000 theoriquement.
En general c'est une section import qui a ce nom la.
Par un break sur GetProcAddress on peut d'ailleurs verifier que c'est ici que ca se passe.
bpx getprocaddress015F:00C8F3C7 FF5518 CALL [EBP+18] <-----------GetProcAddress
015F:00C8F3CA 8945F4 MOV [EBP-0C],EAX
015F:00C8F3CD 837DF400 CMP DWORD PTR [EBP-0C],00
015F:00C8F3D1 7559 JNZ 00C8F42C
015F:00C8F3D3 84DB TEST BL,BL
015F:00C8F3D5 743F JZ 00C8F416
015F:00C8F3D7 52 PUSH EDX
015F:00C8F3D8 53 PUSH EBX
015F:00C8F3D9 57 PUSH EDI
Plus loin en tracant on peut voir :015F:00C8F4A2 2BCA SUB ECX,EDX
015F:00C8F4A4 03052434C900 ADD EAX,[00C93424]
015F:00C8F4AA 8908 MOV [EAX],ECX
015F:00C8F4AC 8B45D8 MOV EAX,[EBP-28]
015F:00C8F4AF 8B55E8 MOV EDX,[EBP-18]
015F:00C8F4B2 8910 MOV [EAX],EDX <--------- a noper
015F:00C8F4B4 EB08 JMP 00C8F4BE
015F:00C8F4B6 8B45D8 MOV EAX,[EBP-28]
015F:00C8F4B9 8B55F4 MOV EDX,[EBP-0C]
015F:00C8F4BC 8910 MOV [EAX],EDX <--------- a noper
015F:00C8F4BE 83C604 ADD ESI,04
015F:00C8F4C1 8345D804 ADD DWORD PTR [EBP-28],04
c.a.d à partir de l'adresse Virtual Offset +lmagebase (appelons le 0044XXXX)
Avec un peu de chance, et surtout un BPM 44XXXX W on devrait pouvoir être averti de la restauration de la
section idata.
Par expérience, et pour avoir déjà étudié les précédente versions
d'Aspack , (et si le principe n'a pas changé depuis), je sais qu'il va lire les données compressées,
les décompresser dans une zone mémoire tampon, puis copier la zone décompressée vers
sa destination d'origine (c'est à dire 0044XXXX)
Et cela, il va le faire autant de fois qu'il y a eu de couches de compression.
Il ne nous restera plus qu'à observer le contenu de la zone en 0044XXXX à chaque break, jusqu'à
ce qu'elle soit propre.
Chaque break devrait donc correspondre à une couche de compression.
On peut ainsi savoir en quelques secondes combien de couches ont été appliquées sur un programme.
Phase 1 : dump manuel de l'Import
Table lorsque celle-ci est " clean "
Il nous faut placer un bpm write à l'adresse ds : 0044XXXX, on va s'arranger pour qu'il y ait un break sous
softIce avant que le loader n'ait commencé à décompresser les données, et en profiter
pour poser le bpm.
Voici la procédure en pas à pas. (c'est long à lire, mais dans la réalité çà
va très vite !)
CRTL+D pour basculer dans Sice
BPX getprocaddress ou BPX getversionexa (bpx getprocaddress est suffisant ici)
X ou F5 pour revenir à Winxx et lancer la cible
POP ! on a un break !
F12 jusqu'à arriver dans le code d'Asprotect.
On désactive le bpx par BD * et on pose notre collet :
BPM ds : 0044XXXX W
(on peut vérifier que la section idata n'a pas encore été touchée en faisant un D 0044XXXX
: il n'y a que des ? ? ? ?)
X ou F5 pour relancer le travail du loader Aspack
POP ! on a un 1er break sur un accès write dans idata en 0044XXXX
On est ici dans le code :
Break due to BPMB #017F:00446000 W DR3
0177:00C0268F F3A5 REPZ MOVS <-- ICI
0177:00C02691 89C1 MOV ECX,EAX
0177:00C02693 83E103 AND ECX,03
0177:00C02696 F3A4 REPZ MOVSB <-- copie le reste de la zone
0177:00C02698 5F POP EDI
0177:00C02699 5E POP ESI
0177:00C0269A C3 RET
Un rapide coup d'œil sur la fenêtre des datas et l'on voit une image import descriptor qui ressemble à
quelque chose de correct.
On fait quand même défiler la fenêtre des datas pour parcourir l'import table jusqu'à
la fin des noms de fonctions, et notre œil maintenant surentraîné nous confirme en quelques secondes
que tout baigne !
Il n'y aurait donc qu'une seule couche de compression sur la section idata. (la version 108.3 n'avait également
qu'une seule couche).
Maintenant on va faire un dump de la mémoire de 0044XXXX à 0044XXXX+2000 ( 2000 bytes de Virtual
Size )
Soit vous faites le dump à l'aide d'IceDump et vous passez ensuite à la phase 2 (dump manuel du programme)
soit vous utilisez Procdump et il faut pour cela " geler " Asprotect de la manière suivante :
A EIP assemble à partir de l'adresse courante
0177:00C02698 jmp eip pour boucler sur la même instruction
ensuite à l'aide de Procdump, sélectionner la cible dans la liste des tâches, clic droit, et
faire un dump partiel de 2000 bytes à partir de 0044XXXX. Sauvegarder sous " ImpData_Asp.dmp "
(par exemple). On n'oublie pas le Kill du task Cible.
(Le fichier ainsi sauvegardé correspond à celui qui a servi de support pour les exemples dans ce
tutoriel : détail d'une import table de type Borland et exercice de reconnaissance visuelle)
Il est temps de passer à la phase 2.
Phase 2 : dump manuel du programme
décompressé avant son lancement
Bien entendu, pour pouvoir effectuer cela, il faut savoir à quel endroit asprotect a terminé son
travail et passe le relais au programme décompressé.
La technique de recherche rapide que j'ai utilisé dans ce cas précis : une technique " visuelle
" (un peu " bestiale " je dois le reconnaître) :
On relance la cible et comme le " bpm 0044XXXX w " est resté actif, on va avoir un break sur l'accès
à la section idata. C'est notre point de départ de la recherche.
On trace rapidement avec F10 sans chercher à comprendre le code qui s'exécute, mais en comptant le
nombre d'appuis et en regardant " l'image " formée par le code, jusqu'au lancement d'Asprotect.
(Cela arrive relativement vite, car il ne reste plus beaucoup de sections à décompresser)
Il ne reste plus qu'à relancer Asprotect, break, et tracer rapidement nfois avec F10 pour arriver sur le
dernier call qui lance l'application.
Une fois le call repéré ( en 0177:00C1151F pour moi ),
0177:00C1151F E874F9FFFF CALL 00C10E98 <-- lance la cible
0177:00C11524 5F POP EDI
0177:00C11525 5E POP ESI
0177:00C11526 5B POP EBX
0177:00C11527 59 POP ECX
0177:00C11528 59 POP ECX
0177:00C11529 5D POP EBP
0177:00C1152A C20400 RET 0004
0177:00C10E98 89C4 MOV ESP,EAX
0177:00C10E9A 89D0 MOV EAX,EDX
0177:00C10E9C 8B1D6C66C100 MOV EBX,[00C1666C]
0177:00C10EA2 89041C MOV [EBX+ESP],EAX
0177:00C10EA5 61 POPAD <-- restaure registres
0177:00C10EA6 50 PUSH EAX <-- push entry point
0177:00C10EA7 C3 RET <-- call entry point
Dérouler le programme jusqu'au RET, notez l'adresse indiquée dans le registre EAX qui correspond
à l'Entry Point du programme (0044295C pour moi)
Et comme précédemment :
A EIP assemble à partir de l'adresse courante
0177:00C10EA7 jmp eip pour boucler sur la même instruction
Désactiver tous les breakpoints par 'BD*'
puis X ou F5
ensuite à l'aide de Procdump ( toujours lui ), sélectionner la cible dans la liste des process, clic
droit et faire un dump Full.
Attention :
avant d'effectuer le Dump, je vous conseille de modifier les options de la manière suivante :
- recompute object (oui)
- optimize (non) afin d'obtenir les raw offset = aux virtual offset (c'est plus facile à lire par la suite)
- rebuild Import table ou don't rebuild import table ? Cela n'a pas d'importance
On peut passer à la phase 3
Phase 3 : insertion du fichier dump
de l'Import Table dans le programme décompressé
Nous avons donc un exécutable décompressé, mais avec une section idata pas good !
Maintenant que vous êtes familier de l'Import Table vous pouvez jeter un œil sur l'exécutable avec
Hexworks30 pour vous apercevoir rapidement qu'il y a quelque chose qui cloche…
En effet ! Si la table à l'air correcte au premier abord, dès que l'on parcourt visuellement l'import
address table on s'aperçoit vite qu'il y a des adresses différentes des autres : il y a des pointeurs
non pas vers les noms de fonctions, mais vers 00C3xxxx (c'est à dire en dehors de notre zone).
Et cela concerne les fonctions de USER32.DLL et KERNEL32.DLL
(Si vous avez choisi l'option " rebuid import table ", une partie des pointeurs est correcte)
Si vous voulez savoir qui, quand, où et comment cela a été fait, il suffit de placer un BPR
0044XXXX 447FFF W juste après le break sur accès à la section idata. Relancer puis attendre
le break révélateur. Il n'y a plus qu'à tracer pour comprendre ce qu'il se passe…
Dans Hexworks30 :
- Charger le fichier dump " ImpData_Aspk.dmp " ainsi que le programme décompressé "
Cible_upk.exe".
- Faire un edit / goto 4XXXX pour vous positionner sur le 1er byte de la section idata.
0044XXXX : parce que le raw offset est identique au virtual offset (grâce au choix de nos options lors du
dump)
- Sélectionner le block , size = 2000 (en hexa) puis delete
- Dans le fichier " ImpData_Asp.dmp " faites la même chose pour sélectionner le bloc de
2000 bytes puis 'copy'
- Activer la fenêtre " Cible_upk.exe" puis 'paste'.
Et voilà le programme Asprotect décompressé, avec une import table tout propre, comme à
son origine.
Il ne reste plus qu'à effectuer les finitions, c'est à dire mettre les bonnes valeurs dans le PE
header et PE directory.
Phase 4 : mise à jour de l'entry
point et des adresses dans la directory Import table
Avec Procdump :
· Mettre la valeur de l'entry point du programme décompressé.
pour moi : 0044295C - 00400000 = 0004295C (valeur relevée dans eax - image de base)
· Modifier les caractéristiques de la section .CODE en " E0…..20 " afin d'obtenir le listing
désassemblé avec Wdasm
· Modifier dans Directory Import Table la valeur pour pointer vers le début de l'image import descriptor
= 4XXXX Modifier la size = 012C (pour connaître la valeur, il suffit de regarder le fichier dump et repérer
la fin de l'image import descriptor)
Si vous ne vous êtes pas trompés en route, vous avez maintenant un dump de programme non seulement
désassemblable avec les import functions, mais en plus il est exécutable.
Il n'y a plus de détection de SoftIce, puisque celle-ci était prise en charge par le loader Asprotect…
Vous pouvez maintenant corriger d'éventuels bugs sur l'application…
Seconde Partie :
Dans le cas de programmes protégés de cette façon, la réalisation d'un script est assez
inefficace (bien que certainement faisable), et un Mémory Dumper comme R !sc Process Patcher semble ne pas
se retrouver dans les méandres d'un code polymorphe.
Reste le patch manuel.
Celui ci n'est pas facilité par le fait que la Ready Made Protection va décompresser/décrypter
l'application par petits morceaux, et qu'il va falloir isoler les uns après les autres.
Pour commencer, il faut connaître l'adresse à patcher dans le programme d'origine. Il serait trivial
ici (j'adore quand les Ricains utilisent cette expression…) de détailler la façon dont je m'y suis
pris pour trouver l'octet que je souhaite modifier dans ART en 00489D0C, mais quoi qu'il en soit, il va falloir
commencer par trouver dans quelle routine et à quel moment l'adresse 00489D0C est décompressée.:00489CFA 8B45F4 mov eax, dword ptr [ebp-0C]
:00489CFD 8B14857C5E4900 mov edx, dword ptr [4*eax+00495E7C]
:00489D04 8B45F0 mov eax, dword ptr [ebp-10]
:00489D07 E82CEAF7FF call 00408738
:00489D0C 85C0 test eax, eax à remplacer par 33 C0
:00489D0E 7506 jne 00489D16
:00489D10 C645FB01 mov [ebp-05], 01
:00489D14 EB0C jmp 00489D22
Dés que vous obtenez un break, il va falloir chercher ensuite, en traçant ave F10 si besoin est,
un espace de 5 octets minimum (longueur nécessaire pour écrire un Jmp XXXXXXXX). En sortant de deux
petits calls, j'ai opté pour créer un branchement en 00C8F2E7 à la place d'un Lea Eax, [Ebp-0C]
(3 octets) et du Mov Ebx [Esi+04] (3 octets) suivant.
A nouveau j'ai placé un Bpm, sur 00C8F2E7 cette fois ci, pour trouver la routine décompressant cette
adresse. PoP !
Break en 00C9C3C3 sur un classique de la décompression, l'instruction Repz Movsb. Quelques lignes plus bas
un Push 00008000 va parfaitement me convenir…:00C9C3C3 F3A5 REPZ MOVSD > décompression
:00C9C3C5 8BC8 MOV ECX,EAX
:00C9C3C7 83E103 AND ECX,03
:00C9C3CA F3A4 REPZ MOVSB
:00C9C3CC 5E POP ESI
:00C9C3CD 8B8535194400 MOV EAX,[EBP+00441935]
:00C9C3D3 6800800000 PUSH 00008000 > 5 octets. Parfait !
:00C9C3D8 6A00 PUSH 00
:00C9C3DA 50 PUSH EAX
:00C9C3DB FF953D194400 CALL [EBP+0044193D]
Reste à trouver de la place pour loger ces 5 Patchs…
Une solution assez empirique, mais qui marche souvent, consiste à rechercher de la place disponible (des
séries de Add Byte Ptr) avant que le crypteur ne passe le relais au programme d'origine (bien qu'il me soit
arrivé de voir des décompresseurs utiliser deux fois le même espace…), de poser un BPM adresse
W ( ou BPR adresse adresse+lg présumée du patch RW) pour vérifier que le programme n'écrira
rien dans la zone convoitée, et de se l'attribuer…
Entre les patchs d'Alchemy (EP + 0x0F40), de Icon Grabber (EP + 0x10A0 -> mais sur l'entête de l'icône
de l'application) ou de ART (EP + 0xA6), vous avez une idée des adresses où vous seriez susceptible
de pouvoir implanter un patch…
Une fois que vous aurez en tête, ou sur le papier, la " gueule " du patch à écrire,
vous pouvez commencer par l'écrire en mémoire à l'adresse repérée. C'est la
phase test….
En général, à ce stade, je me contente de réécrire les octets que j'envisage
de remplacer, juste histoire de voire si le principe va fonctionner. Si c'est OK en mémoire (c'est à
dire si le programme ne plante pas, et que visiblement rien n'a bougé), je peux passer à la phase
suivante, et détourner le programme !:004F50A6 8BB5A8214500 MOV ESI,[EBP+004521A8]
:004F50AC F3A4 REPZ MOVSB
:004F50AE 8B85A8214500 MOV EAX,[EBP+004521A8]
:004F50B4 E9DB110000 JMP 004F6294 > saut vers patch 1
:004F50B9 6A00 PUSH 00
patch 1
:004F6294 C70514534F00E9960F00MOV DWORD PTR [004F5314],000F96E9 > implantation
:004F629E C60518534F0000 MOV BYTE PTR [004F5318],00 > du Jump Patch 2
:004F62A5 6800800000 PUSH 00008000 > restauration des octets modifiés
:004F62AA E90AEEFFFF JMP 004F50B9 > le programme continue sa route
Result of ptach 1
:004F52FF 58 POP EAX
:004F5300 EB01 JMP 004F5303
:004F5302 E8EB01E98B CALL 8C3854F2
:004F5307 741A JZ 004F5323
:004F5309 1403 ADC AL,03
:004F530B F2C1F902 REPNZ SAR ECX,02
:004F530F F3A5 REPZ MOVSD
:004F5311 83C328 ADD EBX,28
:004F5314 E9960F0000 JMP 004F62AF > jump patch 2
:004F5319 8B85A8214500 MOV EAX,[EBP+004521A8]
:004F531F 50 PUSH EAX
patch 2
:004F62AF 803DEFC0C90068 CMP BYTE PTR [00C9C0EF],68 > zone décompressée
:004F62B6 7511 JNZ 004F62C9 > Non -> saute
:004F62B8 C705EFC0C900E9DAA185MOV DWORD PTR [00C9C0EF],85A1DAE9 > oui
:004F62C2 C605F3C0C900FF MOV BYTE PTR [00C9C0F3],FF > modification
:004F62C9 E92AEFFFFF JMP 004F51F8 > reprend le cours normal des choses
Après avoir tracé ces lignes et posé un BPM 00C90EF X, allez contrôler la validité
de vos modifications :Result of patch 2
:00C9C0DB 8DBD051A4400 LEA EDI,[EBP+00441A05]
:00C9C0E1 8BB535194400 MOV ESI,[EBP+00441935]
:00C9C0E7 F3A4 REPZ MOVSB
:00C9C0E9 8B8535194400 MOV EAX,[EBP+00441935]
:00C9C0EF E9DAA185FF JMP 004F62CE > jump patch 3
:00C9C0F4 6A00 PUSH 00
:00C9C0F6 50 PUSH EAX
patch 3
:004F62CE C705D3C3C900E9119F85MOV DWORD PTR [00C9C3D3],859F11E9
:004F62D8 C605D7C3C900FF MOV BYTE PTR [00C9C3D7],FF
:004F62DF 6800800000 PUSH 00008000
:004F62E4 E90B5E7A00 JMP 00C9C0F4
Result of patch 3
:00C9C3C3 F3A5 REPZ MOVSD
:00C9C3C5 8BC8 MOV ECX,EAX
:00C9C3C7 83E103 AND ECX,03
:00C9C3CA F3A4 REPZ MOVSB
:00C9C3CC 5E POP ESI
:00C9C3CD 8B8535194400 MOV EAX,[EBP+00441935]
:00C9C3D3 E9119F85FF JMP 004F62E9 > jump patch 4
:00C9C3D8 6A00 PUSH 00
:00C9C3DA 50 PUSH EAX
:00C9C3DB FF953D194400 CALL [EBP+0044193D]
patch 4
:004F62E9 C605E82CC800C3 MOV BYTE PTR [00C82CE8],C3 > anti-anti-Sice
:004F62F0 6800800000 PUSH 00008000 > restauration
:004F62F5 C705E7F2C800E9217086MOV DWORD PTR [00C8F2E7],867021E9
:004F62FF 66C705EBF2C800FF90 MOV WORD PTR [00C8F2EB],90FF > jump patch 5
:004F6308 E9CB607A00 JMP 00C9C3D8 > retour
Result of patch 4
:00C8F2E4 FF550C CALL [EBP+0C]
:00C8F2E7 E9217086FF JMP 004F630D > jump to patch 5
:00C8F2EC 90 NOP > octet de rééquilibrage
:00C8F2ED FF5518 CALL [EBP+18]
:00C8F2F0 83C60C ADD ESI,0C
:00C8F2F3 837E0400 CMP DWORD PTR [ESI+04],00
:00C8F2F7 0F8535FFFFFF JNZ 00C8F232
:00C8F2FD 837DF000 CMP DWORD PTR [EBP-10],00
:00C8F301 7408 JZ 00C8F30B
patch 5
:004F630D C6050C9D480033 MOV BYTE PTR [00489D0C],33 > patch
:004F6314 8D45F4 LEA EAX,[EBP-0C] > restauration des octets
:004F6317 8B5604 MOV EDX,[ESI+04] > modifié par le jmp to patch5
:004F631A E9CE8F7900 JMP 00C8F2ED > et on continue …
Il reste que cette Ready Made Protection, qui semble se généraliser actuellement, mériterait qu'on lui crée un Unpacker. J'espère que nous n'en resterons pas là…
Bonne Journée
Christal