I_Introduction |
Christal m'a demandé de refaire ma copie sur le dump de pleditor qui
n'était, il est vrai, pas très expliqué. Mais cette fois il m'a demandé si
je pouvais le faire sur un autre programme, histoire de vérifier si elle marche
toujours. Donc cette essai visera à expliquer comment obtenir un dump
fonctionnel d'un programme protégé avec asprotect 1.0 (ou plus). En fait il y
a plusieurs versions d'asprotect et les deux progs présentés ici n'utilisent
pas la même version (enfin c'est quasiment la même). Je vais en profiter pour
parler un peu plus d'asprotect lui-même. TeeJi m'ayant proposé son aide il
vous expliquera une partie du fonctionnement d'asprotect (voir son essai). Enfin
trois méthodes de dump seront présentés ici: tout d'abord celle que j'ai
utilisé sur pleditor mais revu et corrigé et celle mise au point sur commview
par Tsehp . Une autre méthode (enfin deux pour être exact) expliquera un moyen
de reconstruire une table d'import.
En fin de ce tutoriel je vous indiquerait des tuts à lire qui pourrait vous
aider à mieux comprendre (certain sur aspr et d'autre sur cdilla et d'autres
sur le format pe).
II_Tools |
Softice 4.01
Icedump 6.01
Procdump 1.6.2 (ou Procinfos)
Ida 4.04 (pas nécessaire)
Winhex 9.4 (ou autre)
Pe-rebuilder (ou un autre rebuilder)
III_1ère méthode: pleditor |
Dans le dossier sur Aspack/Asprotect j'avais proposé une méthode pour obtenir un dump fonctionnel de PerL Editor 3.0 build 15.06.2000. J'avais dit que cette méthode ne marchait pas mais en fait si. Je vais la refaire ici mais d'une autre facon. De plus je vais essayer le l'expliqué plus clairement.
Asprotect qu'est-ce que c'est? et bien c'est une protection. Elle permet de
mettre sur son programme des limitations (temps, utilisation ...), de mettre des
anti-debuggers, de mettre du code polymorphe ou overlaping du code,de multi
compressé/cryptés le prog et d'empêcher le dump du programme. Il y a
également d'autres fonctions mais elles ne nous préoccupe pas ici (on peut
notamment cryptés certaine partie du prog qui se décrytpe avec une clef).
Comment peut-on s'en sortir? et bien c'est plus ou moins simple. Une fois que
l'on sait faire ca devient simple :).
Bon tout d'abord qu'est ce qui empêche le dump du programme? et bien en fait
asprotect ne recré pas une table d'import et une iat (import address table)
correct lors de la décompression en mémoire du programme (une partie de ce tut
est consacré à ceci).
1) Récolte d'informations et dump.
Bon tout d'abord on va regarder les sections de pleditor. Ceci v nous permettre de savoir où asprotect devrait normallement mettre la table d'import du programme. Donc on prend Procdump et on va dans Pe-Editor et sections et on a ceci :
Name: CODE VS: 000B3000 VA:
00001000 RS: 00042800 RA: 00000400 C: C0000040
Name: DATA VS: 00002000 VA:
000B4000 RS: 00000C00 RA: 00042C00 C: C0000040
Name: BSS VS: 00002000 VA:
000B6000 RS: 00000000 RA: 00043800 C: C0000040
Name: .idata VS: 00003000 VA: 000B8000 RS:
00000200 RA: 00043800 C: C0000040
Name: .tls VS: 00001000 VA:
000BB000 RS: 00000000 RA: 00043A00 C: C0000040
Name: .rdata VS: 00001000 VA: 000BC000 RS:
00000200 RA: 00043A00 C: C0000040
Name: .reloc VS: 0000D000 VA: 000BD000 RS:
00000000 RA: 00043C00 C: C0000040
Name: .rsrc VS: 00036000 VA: 000CA000
RS: 0000A200 RA: 00043C00 C: C0000040
Name: .data VS: 00015000 VA: 00100000
RS: 00014800 RA: 0004DE00 C: C0000040
Name: .data VS: 00001000 VA: 00115000
RS: 00000000 RA: 00062600 C: C0000040
On trouve une section .idata et c'est ici que doit se trouvé une table d'import (pour les exe avec sections .idata). Donc l'import address table correspond au virtual offset de la section .idata (b80000). Donc on sait qu'il va mettre sa table d'import en 4b8000 (virtual address + image base). Il faudra donc mettre un point d'arrêt quand il écrira dans cette partie : bpm 4b8000 w .
Maintenant que l'on sait où asprotect va mettre la table d'import on peut commencer à tracer le programme. Le point d'entrée du programme est en 500001, c'est la que se trouve la première partie d'asprotect (et oui il y a une multi-compression). Mais cette version 1.0 d'asprotect permet de décompressé ces parties en mémoires à des addresses différentes. Il va donc falloir enlever cela (thx sword). Pour ce faire on pose un bpx gettickcount et on lance le programme. Softice apparait et on appuie sur F12 pour revenir dans le code du prog, on doit normallement arriver là :
015F:00500223
C3
RET
015F:00500224
25FFFF0100
AND
EAX,0001FFFF <-- on arrive là
015F:00500229
EB04
JMP 0050022F
015F:0050022B
E8EB04E9EB
CALL EC39071B
015F:00500230
FB
STI
015F:00500231
E950EB04E8
JMP E854ED86
015F:00500236
EB04
JMP 0050023C
015F:00500238
E9EBFBE9E8
JMP E939FE28
015F:0050023D
0300
ADD EAX,[EAX]
015F:0050023F
0000
ADD [EAX],AL
Donc pour ce débarraser du changement d'adresse il faut mettre eax à 0 (ou autre chose mais tjs pareil), ensuite on trouve la fin de la première zone en 500c28 :
015F:00500C17
E8EB04E9EB
CALL EC391107
015F:00500C1C
FB
STI
015F:00500C1D
E95BEB04E8
JMP E854F77D
015F:00500C22
EB04
JMP 00500C28
015F:00500C24
E9EBFBE9C3
JMP C43A0814
015F:00500C28
C3
RET
<-- fin de la 1ère zone
015F:00500C29
B0DD
MOV AL,DD
015F:00500C2B
54
PUSH ESP
015F:00500C2C
004765
ADD [EDI+65],AL
015F:00500C2F
7454
JZ 00500C85
Et là il passe à la deuxième zone qu'il a décompressé en mémoire. On arrive là :
015F:0056407C
90
NOP
015F:0056407D
60
PUSHAD
<-- on arrive là
015F:0056407E
E844060000
CALL 005646C7
015F:00564083
EB44
JMP 005640C9
015F:00564085
0000
ADD [EAX],AL
015F:00564087
0000
ADD [EAX],AL
015F:00564089
0000
ADD [EAX],AL
015F:0056408B
0000
ADD [EAX],AL
015F:0056408D
87DB
XCHG EBX,EBX
015F:0056408F
90
NOP
Puis il faut tracer jusqu'à la fin de cette deuxième zone. Pour y arriver plus rapidement on peut poser un bpm eip+5bb x . On arrive ici:
015F:00564632
0385A0304400
ADD EAX,[EBP+004430A0]
015F:00564638
5B
POP
EBX
<-- on tombe là
015F:00564639
0BDB
OR EBX,EBX
015F:0056463B 8985D92E4400
MOV [EBP+00442ED9],EAX
015F:00564641
61
POPAD
015F:00564642
7508
JNZ 0056464C
015F:00564644
B801000000
MOV EAX,00000001
015F:00564649
C20C00
RET 000C
015F:0056464C
68B8665500
PUSH 005566B8
015F:00564651
C3
RET
<-- fin 2ème zone
Donc en 564651 il va vers la troisième zone mémoire. On y va nous aussi :
015F:005566B5
8D4000
LEA EAX,[EAX+00]
015F:005566B8
55
PUSH
EBP
<-- on est là
015F:005566B9
8BEC
MOV EBP,ESP
015F:005566BB
83C4F4
ADD ESP,-0C
015F:005566BE
E86DFBFEFF
CALL 00546230
015F:005566C3 0F858F09FFFF
JNZ 00547058
015F:005566C9
E8460EFFFF
CALL 00547514
015F:005566CE
E84537FFFF
CALL 00549E18
015F:005566D3
E81C55FFFF
CALL 0054BBF4
015F:005566D8
E8A3AAFFFF
CALL 00551180
015F:005566D8
E8A3AAFFFF
CALL 00551180
015F:005566DD
E87609FFFF
CALL
00547058 <-- on rentre dans
ce call
015F:005566E2
8BE5
MOV ESP,EBP
015F:005566E4
5D
POP EBP
015F:005566E5
C20C00
RET 000C
015F:005566E8
0000
ADD [EAX],AL
015F:005566EA
0000
ADD [EAX],AL
015F:005566EC
0000
ADD [EAX],AL
015F:005566EE
0000
ADD [EAX],AL
015F:005566F0
0000
ADD [EAX],AL
Donc voici le début de la troisième zone mémoire d'asprotect, il faut toujours rentrer dans le dernier call pour s'occuper de l'anti debugger. Une fois que l'on est rentré dans ce call on rentre dans le 2ème que l'on rencontre et on trace pour arriver là :
015F:00555674
8B1574C75500
MOV EDX,[0055C774]
015F:0055567A
8B45F8
MOV EAX,[EBP-08]
015F:0055567D
E832E6FFFF
CALL 00553CB4
015F:00555682 8A15DCB65500
MOV DL,[0055B6DC]
015F:00555688
8B45F8
MOV EAX,[EBP-08]
015F:0055568B
E890E6FFFF
CALL
00553D20
<-- call anti-debugger
015F:00555690
8945F4
MOV [EBP-0C],EAX
015F:00555693
837DF400
CMP DWORD PTR [EBP-0C],00
015F:00555697
7428
JZ 005556C1
015F:00555699
8B45F4
MOV EAX,[EBP-0C]
Le call en 55568b est l'anti-debugger ensuite il met eax dans ebp-0c et regarde si c'est 0. Donc je remplace le call par mov eax, 0 (on peut simplement mettre 0 dans eax à la sortie du call). Ensuite on appuie sur F5 et softice devrait réapparaitre. En effet j'ai dit au tout début de mettre un bpm 4b8000 w pour savoir quand il écrira dans la zone de l'import table. On doit arriver ici :
015F:00545709
782A
JS 00545735
015F:0054570B
F3A5
REPZ
MOVSD
<-- on arrive là
015F:0054570D
89C1
MOV ECX,EAX
015F:0054570F
83E103
AND ECX,03
015F:00545712
F3A4
REPZ MOVSB
015F:00545714
5F
POP EDI
015F:00545715
5E
POP ESI
015F:00545716
C3
RET
015F:00545717
8D740EFC
LEA ESI,[ECX+ESI-04]
015F:0054571B
8D7C0FFC
LEA EDI,[ECX+EDI-04]
Donc il copie ce qu'il y a en esi vers edi. Or edi == 4b8004 et esi == C72824. Ici il copie vers la zone où devrait être la table d'import des 00. Mais comment ce fait-il qu'il utilise une zone en c70000. On va regarder avec softice ce qu'il y a :
0167:00C72500 97 1F FB 4D B4 B2 65 77-1B 59 3D 6C ED E4 00 D0
...M..ew.Y=l....
0167:00C72510 88 0B 00 01 69 6D 6D 33-32 2E 64 6C 6C 00 01 18
....imm32.dll...
0167:00C72520 8C 90 8F 6D 2A 4B F0 E9-95 CE F8 38 EC 02 E5 C9
...m*K.....8....
0167:00C72530 CB 82 44 A9 C1 4D E5 EA-01 17 8C 90 8F 6D 2A 4B
..D..M.......m*K
Bizare on trouve des références à des dlls. Il faut garder cela en tête. On peut enlever le point d'arrêt en écriture car il va encore y écrire par la suite mais dans cette partie nous n'iront pas plus loin. Donc on continue de tracer (f10) pour arriver au moment ou asprotect passe la main au prog décompressé en mémoire. On arrive normalement là :
015F:00555DE9
8B4508
MOV EAX,[EBP+08]
015F:00555DEC
8B10
MOV EDX,[EAX]
015F:00555DEE
8B4508
MOV EAX,[EBP+08]
015F:00555DF1
8B401C
MOV EAX,[EAX+1C]
015F:00555DF4
E887F6FFFF
CALL
00555480
<-- on rentre là
015F:00555DF9
5F
POP EDI
015F:00555DFA
5E
POP ESI
015F:00555DFB
5B
POP EBX
015F:00555DFC
5D
POP EBP
015F:00555DFD
C20400
RET 0004
Bon on reconnait facilement ce call grace au fait qu'il récupère les registres juste après. Il faut donc y rentrer, sinon le prog se lance. Ensuite on arrive là :
015F:005554A4
B854775400
MOV EAX,00547754
015F:005554A9
E8AE3CFFFF
CALL 0054915C
015F:005554AE
E86D0AFFFF
CALL
00545F20 <-- on évite ce call
015F:005554B3
33C0
XOR EAX,EAX
015F:005554B5
5A
POP EDX
015F:005554B6
59
POP ECX
015F:005554B7
59
POP ECX
015F:005554B8
648910
MOV FS:[EAX],EDX
015F:005554BB
EB0A
JMP 005554C7
015F:005554BD
E93208FFFF
JMP 00545CF4
Et là il faut eviter de passer ce deuxième call sinon il utilise des sehs pour lancer le prog et on s'y pert. Don soit on le nop soit on fait r eip eip+5 quand on est dessus. Ensuite on à la même chose plus loin :
015F:005554D9
B854775400
MOV EAX,00547754
015F:005554DE
E8793CFFFF
CALL 0054915C
015F:005554E3
E8380AFFFF
CALL
00545F20 <-- on évite
015F:005554E8
33C0
XOR EAX,EAX
015F:005554EA
5A
POP EDX
015F:005554EB
59
POP ECX
015F:005554EC
59
POP ECX
015F:005554ED
648910
MOV FS:[EAX],EDX
015F:005554F0
EB0A
JMP 005554FC
015F:005554F2
E9FD07FFFF
JMP 00545CF4
On fait pareil r eip eip+5 ou on nop. Il reste encore un double call, ou on évite le deuxième :
015F:00555534
648920
MOV FS:[EAX],ESP
015F:00555537
33C9
XOR ECX,ECX
015F:00555539
B201
MOV DL,01
015F:0055553B
B854775400
MOV EAX,00547754
015F:00555540
E8173CFFFF
CALL 0054915C
015F:00555545
E8D609FFFF
CALL
00545F20
<-- on évite
015F:0055554A
33C0
XOR EAX,EAX
015F:0055554C
5A
POP EDX
015F:0055554D
59
POP ECX
015F:0055554E
59
POP ECX
De même on fair r eip eip+5 quand on est sur le call. Maintenant on continue de tracer pour arriver ici :
015F:00555590
EBE8
JMP 0055557A
015F:00555592
61
POPAD
<-- eax a l'eip
015F:00555593
EB01
JMP 00555596
015F:00555595
E850EB02E9
CALL E95840EA
015F:0055559A
17
POP SS
015F:0055559B
E802000000
CALL 005555A2
015F:005555A0
E91758803D
JMP 3DD5ADBC
015F:005555A5
88C7
MOV BH,AL
015F:005555A7
55
PUSH EBP
015F:005555A8
0000
ADD [EAX],AL
Ici on retrouve le popad qui est caractéristique d'une fin de décompression . Et en effet à ce moment eax contient l'eip du prog décompressé en mémoire et plus loin un ret nous y envoie. Mais avant de faire quelquechose regardons donc l'état de la zone en 4b8000 ou devrait être la table d'import :
015F:004B8180 F4 49 C7 00 00 4A C7 00-0C 4A C7 00 18 4A C7 00
.I...J...J...J..
015F:004B8190 24 4A C7 00 30 4A C7 00-3C 4A C7 00 48 4A C7 00
$J..0J..<J..HJ..
015F:004B81A0 54 4A C7 00 60 4A C7 00-6C 4A C7 00 78 4A C7 00
TJ..`J..lJ..xJ..
015F:004B81B0 84 4A C7 00 90 4A C7 00-9C 4A C7 00 A8 4A C7 00
.J...J...J...J..
Et qu'est-ce qu'on y retrouve et bien des références vers la zone c7xxxx.
Et oui il utilise le même principe (bien que différent) que cdilla. Pour
appeler une api il fait call [4b8180], qui appel ce qu'il y a en c749f4 et là
on a un truc du genre jmp api. En fait c'est plus complexe (voir la partie du
tut qui en parle), il décrypte certaines choses pour avoir l'api. Mais cela il
le fait quand le programme a besoin de cette api. Donc l'idée est de rajouter
cette partie au programme donc il faut dumper ce qu'il y a en c7xxxx. Pour ce
faire il faut savoir où commence cette zone et où elle se termine. On regarde
sous softice et on voit (entre des zones mémoires invalides) qu'elle commence
en c70000 et qu'elle se termine en c78000. Donc On peut dumper cette zone en
utilisant icedump (extension pour softice) et en faisant : pagein d c70000
8000 c:\import.dat . On obtiendra ainsi un dump de cette zone dans le
fichier import.dat.
Cependant il ne faut pas ce précipiter et dumper le prog ensuite directement.
En effet si on analyse cela de plus près et qu'on laisse le programme ce
dérouler on constate que quand il fait appel à la zone en c7xxxx il fait
également appel à une autre zone. Voici un exemple :
:00C74C32
0000
ADD [EAX],AL
:00C74C34
1E
PUSH DS
:00C74C35
0000
ADD [EAX],AL
:00C74C37
006842
ADD [EAX+42],CH
:00C74C38
68424CC700
PUSH 00C74C42
:00C74C3D
E84A048EFF
CALL
0055508C
<-- il appel une zone mémoire en 55508c
:00C74C42
7A2C
JP 00C74C70
:00C74C44 C7009A2CC700
MOV DWORD PTR [EAX],00C72C9A
:00C74C4A C7002A560000
MOV DWORD PTR [EAX],0000562A
:00C74C50
1E
PUSH
On voit qu'il se sert d'une zone mémoire situé en 55508c. Or ceci
correspond au endroit ou asprotect se décompresse. Donc pour que le programme
marche la zone d'asprotect doit aussi être présente. Il faut donc la dumper
aussi. Comme tout à l'heure on repère toute la zone entre des zones mémoires
invalides, mais attention ici certaine partie sont encore invalide, donc il faut
bien regarder. On trouve que cette zone s'étend de 520000 à 567000, il faut
donc la dumper comme ceci : pagein d 520000 44000 c:\asprotect.dat .
Maintenant on a la zone d'asprotect dans le fichier asprotect.dat .
On peut maintenant passer au dump du programme par lui même. Au niveau du popad
on fait a eip pour modifier l'instruction et on met jmp eip pour qu'il boucle
sur lui-même. Ensuite f5 pour sortir du programme et il reste a dumper le
programme avec procdump en faisant un dump full sur la cible en mémoire.
2) Reconstruction du dump
Maintenant avec tout cela il faut reconstituer un exe valide. J'avais utilisé une première méthode très peu détaillé et assez compliqué. Un jour TeeJi m'a demandé pourquoi j'avais fait cela et pourquoi je n'avais pas utilisé les sections ? heu oui pourquoi ... heu ... il faut le temps que ca monte au cerveau ... lol :). En fait je n'y est pas pensé une seule seconde. Donc la je vais le faire ca va être nettement plus simple.
Bon tout d'abord il faut faire une petite manipulaiton. Il faut copier la section d'asprotect (celle qui est déjà dans le fichier) du prog d'origine (le protégé) et le mettre sur celle de notre dump. On fait ca car sinon la table d'import (celle utilisé par asprotect pas celle dont nous parlions) est abimé et on ne peut pas utiliser le programme dumpé. Pour savoir ou est le début de cette zone on cherche avec un héditeur hexa 90 60 E8 01 00 00 00 90 5D c'est le début de la zone d'asprotect (toujours) et on copi de la à la fin du prog, et on le remet à la place de celle du programme dumpé (on cherche la même chaine pour être au début).
Ensuite les deux autres dumps que l'on a fait il faut les rajoutés au programme. Cependant il faut qu'il soit remis dans les zones mémoires où ils se trouvaient. Pour ce faire on va utiliser procdump, puis peeditor et on ouvre notre programme dumpé. On va dans section et on fait add section. On rajoute une section avec ces caractéristiques :
Name: .aspr VS: 00047000 VA: 00120000 RS: 00047000 RA: 00115400 C: E0000020
C'est la section pour le fichier asprotect.dat. On met une virtual adresse de
120000 (plus l'image base == 520000) pour qu'il se trouve au bon endroit en
mémoire. La taille du dump était de 47000h donc on met 47000h. La RA est
géré par procdump (ca de moins à faire :) ) et on peut changer les
caractéristiques de la section mais ce n'est pas obligatoire.
Ensuite On rajoute la section pour import.dat :
Name: .import VS: 00008000 VA: 00870000 RS: 00008000 RA: 0015C400 C: E0000020
Même explication que précédement. Ensuite il ne faut pas oublié de changer la taille de l'image. Je l'avoue je n'e m'en préoccupe pas car j'utilise procinfo (de TeeJi) qui le fait à ma place. Mais vous il faut le faire :) en utilisant la taille rajouté dans les sections que l'on rajoute à la taille de l'image.
Bon maintenant que ceci est fait, on prend son éditeur hexa et on ouvre le fichier asprotect.dat et on copi tout. On insère cela à la fin du dump de pleditor. On fait ensuite la même chose avec le fichier import.dat. On sauvegarde le dump et on change l'eip (celui contenu dans eax au niveau du popad), ici on fait 4b327c - 400000 (image base) = b327c. Donc le nouvel eip est b327c, on le change soit avec l'éditeur hexa soit avec pe-editor de procdump.
Maintenant on lance le dump et il se lance. CooL :). On a enfin notre dump
qui est executable. Il ne nous reste plus qu'à cracker le dump ...
III_2ème méthode: Advanced mail list verify 2.0 |
N'ayant pas réussi à utiliser la méthode précédente sur amv, je vais vous présenter une méthode que Tsehp a utilisé sur commview. Remarquez que cette méthode ne marche pas sur pleditor (enfin Tsehp l'a changé: voir partie4). Cette méthode est notamment utilisé avec cdilla: c'est ce qu'on appel un "call fixer". Il permet de mettre les adresses des apis (enfin des fonctions) dans la table d'import (enfin ici dans l'iat). Donc en fait on aura toujours pas une iat et une table d'import correct au vrai sens du terme mais on n'aura plus besoin de dumper le chunk de l'it et la partie d'asprotect.
Regardons d'abord un peu comment de présentent les sections du programmes :
Name: .text VS: 00028000 VA: 00001000
RS: 00012200 RA: 00001000 C: C0000040
Name: .rdata VS: 00003000 VA: 00029000 RS:
00000C00 RA: 00013200 C: C0000040
Name: .data VS: 0000D000 VA: 0002C000
RS: 00001400 RA: 00013E00 C: C0000040
Name: .rsrc VS: 00029000 VA: 00039000
RS: 00019000 RA: 00015200 C: C0000040
Name: .data VS: 00015000 VA: 00062000
RS: 00014E00 RA: 0002E200 C: C0000040
Name: .data VS: 00001000 VA: 00077000
RS: 00000000 RA: 00043000 C: C0000040
Ici point de section .idata mais une section .rdata donc la table d'import du
prog décompressé aurait du (si elle existait) se trouvé dans la section
.rdata. On va donc mettre un point d'arrêt sur le début de la section
(lorsqu'il va y écrire) : bpm 429000 w . Ensuite il faut tracer comme
tout à l'heure (je ne réexplique pas c'est identique) jusqu'au début de la
zone trois d'asprotect. là on vire de la même facon le debugger, et softice
réagit de même pour le point d'arrêt en écriture sur le début de la section
.rdata. On trouve cette fois ci que le chunk de "l'import table" est
compris entre de0000 et de4000.
Bon on enlève notre point d'arrêt et on trace jusqu'à la fin (identique à
tout à l'heure à l'exception des doubles calls qui n'y sont pas). On passe
ensuite au début du programme :
015F:00421BFB
C3
RET
015F:00421BFC
55
PUSH
EBP
<-- début du prog
015F:00421BFD
8BEC
MOV EBP,ESP
015F:00421BFF
6AFF
PUSH FF
015F:00421C01
6860944200
PUSH 00429460
015F:00421C06
68985B4200
PUSH 00425B98
015F:00421C0B 64A100000000
MOV EAX,FS:[00000000]
015F:00421C11
50
PUSH EAX
015F:00421C12 64892500000000
MOV FS:[00000000],ESP
015F:00421C19
83EC58
SUB ESP,58
Au passage on en profite pour noté que l'eip est 421bfc. On continue de tracer pour arriver là :
015F:00421C19
83EC58
SUB ESP,58
015F:00421C1C
53
PUSH EBX
015F:00421C1D
56
PUSH ESI
015F:00421C1E
57
PUSH EDI
015F:00421C1F
8965E8
MOV [EBP-18],ESP
015F:00421C22 FF1574914200
CALL
[00429174] <-- intérressant
015F:00421C28
33D2
XOR EDX,EDX
015F:00421C2A
8AD4
MOV DL,AH
015F:00421C2C 8915B86B4300
MOV [00436BB8],EDX
015F:00421C32
8BC8
MOV ECX,EAX
Là on retrouve ce que j'avais expliqué pour pleditor. Il appel ce qu'il y a normalement dans la table d'import (ici en 429174 au début de la section .rdata) et on trouve en 429174, de2464. Donc il va appelé ce qu'il y a en de2464 :
015F:00DE2464
E993091BBF
JMP KERNEL32!GetVersion
015F:00DE2469
1B00
SBB EAX,[EAX]
015F:00DE246B
000E
ADD [ESI],CL
015F:00DE246D
0000
ADD [EAX],AL
015F:00DE246F
00E9
ADD CL,CH
015F:00DE2471
37
AAA
015F:00DE2472
A11ABF1B00
MOV EAX,[001BBF1A]
Et en de2464 c'est ce jump api. Voilà comment il retrouve ces fonctions. Mais ce n'est pas tout je vous ai dit qu'il en décrypté au runtime. On continue de tracé le programme et voici un endroit intérressant (on cherche tjs un call [429xxxx] ) :
015F:0040B8DB
51
PUSH ECX
015F:0040B8DC
52
PUSH EDX
015F:0040B8DD FF1508904200
CALL
[00429008]
<-- on a un call intérressant
015F:0040B8E3
85C0
TEST EAX,EAX
015F:0040B8E5
7408
JZ 0040B8EF
015F:0040B8E7
8B44241C
MOV EAX,[ESP+1C]
015F:0040B8EB
33F6
XOR ESI,ESI
015F:0040B8ED
8907
MOV [EDI],EAX
Pourquoi ce call [00429xxxx] est-il plus intérressant pour nous que d'autres? et bien voici ce qu'il fait par la suite :
015F:00DE2A14
681E2ADE00
PUSH
00DE2A1E
<-- on arrive là si on rentre dans le call
015F:00DE2A19
E816246DFF
CALL
004B4E34
<-- il appel une partie d'asprotect
015F:00DE2A1E
851F
TEST [EDI],EBX
015F:00DE2A20
DE00
FIADD WORD PTR [EAX]
015F:00DE2A22
AE
SCASB
015F:00DE2A23
1F
POP DS
015F:00DE2A24
DE00
FIADD WORD PTR [EAX]
015F:00DE2A26
DE00
FIADD WORD PTR [EAX]
Et bien il va vers la même zone mémoire que tout à l'heure mais là il ne fait pas jmp api, mais call 004b4e34. C'est une partie qui est dans la zone d'asprotect. Voilà on a trouvé le call qui sert à décrypter les api. Il est très important, il faut le noter.
Bon maintenant on recommence on lance le programme, on vire le changement d'adresse, on passe les décompressions on arrive dans la troisième zone, on vire l'anti-debugger et on oubli pas de remmettre notre bpm 429000 w. Normalement softice devrait s'arrêter sur le repz mosb de tout à l'heure, là où il copiait des zéro. Mais cette fois ci on laisse ce point d'arrêt et on appuis sur f5. Là softice devrait s'arrêté ici :
015F:004B5138
87FE
XCHG EDI,ESI
015F:004B513A
AC
LODSB
015F:004B513B
08C0
OR AL,AL
015F:004B513D
74E4
JZ 004B5123
015F:004B513F
4E
DEC ESI
015F:004B5140
56
PUSH ESI
015F:004B5141
53
PUSH EBX
015F:004B5142
80F802
CMP AL,02
015F:004B5145
7407
JZ 004B514E
015F:004B5147
0FB64E01
MOVZX ECX,BYTE PTR [ESI+01]
015F:004B514B
41
INC ECX
015F:004B514C
EB05
JMP 004B5153
015F:004B514E
B904000000
MOV ECX,00000004
015F:004B5153
41
INC ECX
015F:004B5154
01CE
ADD ESI,ECX
015F:004B5156
E8B5FDFFFF
CALL
004B4F10
<-- cherche les apis
015F:004B515B
AB
STOSD
015F:004B515C
EBDC
JMP
004B513A
<-- retourne au début
015F:004B515E
61
POPAD
Et là on tombe dans une boucle. Si on regarde on trouve dans edi des trucs
du genre 429004. En gros cette routine sert à écrire dans la table d'import
normal (en 429000) les références à la table d'import en de0000. Donc l'idée
ici est qu'il n'écrive pas en 429000 des références à la zone en de0000,
mais qu'il mette directement les adresses des apis qui seront utilisés. Et
c'est pour cela que tout à l'heure on a cherché le call 4b4e34 qui sert à
décrypter les adresses des apis.
C'est donc la qu'on va mettre le call fixer. Remarquez que si vous avez la
flemme Tsehp propose de trouver le call 4b4e34 en faisant S 0 L FFFFFFFF 55 8b
ec c4 f8 fe ff. Pour cette boucle on peut la trouver avec S 0 L FFFFFFFF AC 08
c0 74 E4. Mais bon il est utile de savoir d'où cela provient. Maintenant il
faut avant que cette boucle ne s'éxécute écrire un call fixer. Et c'est là
que l'on dit merci Tsehp :). En effet il proposait cette méthode sur le
programme commview. Donc voici le principe :
015F:004B514E
B904000000
MOV ECX,00000004
015F:004B5153
41
INC ECX
015F:004B5154
01CE
ADD ESI,ECX
015F:004B5156
E8B5FDFFFF
CALL
004B4F10
<-- on dévit là
015F:004B515B
AB
STOSD
015F:004B515C
EBDC
JMP
004B513A
<-- saut pour la boucle
Ce qu'il faut faire c'est dévié au niveau du call et chercher un espace libre en mémoire (avec des 0000) pour y dévié le call et y rajouté notre code. Je l'ai dévié en 4b6500 :
015F:004B5147
0FB64E01
MOVZX ECX,BYTE PTR [ESI+01]
015F:004B514B
41
INC ECX
015F:004B514C
EB05
JMP 004B5153
015F:004B514E
B904000000
MOV ECX,00000004
015F:004B5153
41
INC ECX
015F:004B5154
01CE
ADD ESI,ECX
015F:004B5156
E9A5130000
JMP
004B6500 <-- on dévit
015F:004B515B
AB
STOSD
015F:004B515C
EBDC
JMP 004B513A
015F:004B515E
61
POPAD
Et en 4b6500 on met le call fixer que Tsehp nous proposait (thx man :) ). Voici ce qu'il proposait comme call fixer :
190000 call
18a2f4
; we use the legal call
190005 cmp
ecx,40000000 ; ecx
contains an api address ?
19000b jle
19001c
; if not
19000d add
ecx,eax
; if yes convert the address***case 2
19000f add
ecx,5
; to the absolute api address for a normal import table
190012 mov dword ptr [edi],ecx ; put it in import table, pointed
by edi
190014 add
edi,4
; updates edi to the next import
190017 jmp
18a51e
; back to normal
19001c cmp
ecx,0
; is it case 4 ?
19001f jz 19002e
190021 cmp
eax,40000000 ; eax
contains an api address ?
190026 jle
19003c
; if not go to 19003c
190028
stosd
; we're in case 1, direct copy of valid api address in import table
190029 jmp
18a51e
; back to normal
19002e push dword ptr [eax+1] ; we're in case 3, pushes
the encrypted api address
190031 call
16df80
;IMPORTANT
you have to locate this aspack address by doing a S 0 L FFFFFFFF 55 8b ec 81
c4 f8 fe ff ff 53 56
so we found as example 16df80, you MUST modify the code at offset 16df80+95
with three nops (90), if you don't
this call will generate an error 13 in aspack (the call doesn't return
properly)
this call is used to decypher the encrypted api address, normally decrypted
at run time
190036
stosd
; the api's address is then decrypted, copy it to import table
190037 jmp
18a51e
; back to normal, don't forget that this is the loop back jump
19003c mov eax,KERNEL32!GetProcAddress ; use softice
to have your system's getprocaddress. this is case 4
190041
stosd
; copy to import table
190042 jmp
18a51e
; back to normal
Bon mais ici ce call fixer ne fonctionne pas. Il va falloir le modifer, notamment au niveau du cmp ecx, 0 qui empêche de passer par le cmp eax, 40000000. Mais avant cela il reste quelquechose qui l'empécherait de toute facon de marcher. Ne nous préoccupons pas de la table d'import pour le moment et faisons le dump du prog (comme tout à l'heure mais cette fois juste le programme). Analysons donc un peut le dump que nous avons obtenu et regardons notamment le début de la section .rdata :
00029000 DC29 DE00 F829 DE00 142A DE00 302A DE00 .)...)...*..0*..
00029010 4C2A DE00 682A DE00 842A DE00 A02A DE00 L*..h*...*...*..
00029020 BC2A DE00 0000 0000 482B DE00 542B DE00 .*......H+..T+..
00029030 602B DE00 6C2B DE00 782B DE00 842B DE00 `+..l+..x+...+..
00029040 0000 0000 4228 F2BF FE24 F2BF D824 F2BF ....B(...$...$..
00029050 3A28 F2BF 8A28 F2BF 9322 F2BF 3551 F2BF :(...(..."..5Q..
00029060 144A F2BF 7B50 F2BF 8D14 F2BF 7F26 F2BF .J..{P.......&..
00029070 0000 0000 7021 DE00 7C21 DE00 8821 DE00 ....p!..|!...!..
00029080 9421 DE00 A021 DE00 AC21 DE00 B821 DE00 .!...!...!...!..
00029090 C421 DE00 D021 DE00 DC21 DE00 E821 DE00 .!...!...!...!..
000290A0 F421 DE00 0022 DE00 0C22 DE00 1822 DE00
.!..."..."..."..
000290B0 2422 DE00 3022 DE00 3C22 DE00 4822 DE00
$"..0"..<"..H"..
000290C0 5422 DE00 6022 DE00 203D 4B00 6C22 DE00 T"..`"..
=K.l"..
000290D0 7822 DE00 8422 DE00 9022 DE00 9C22 DE00
x"..."..."..."..
000290E0 A822 DE00 B422 DE00 C022 DE00 CC22 DE00
."..."..."..."..
On retrouve bien évidemment des références à la zone dexxxx. Mais en 290c8 (4290c8 en mémoire) on trouve 4b3d20, or ceci fait partie de la section d'asprotect. Comment ce fait-il qu'il mette un lien vers cette zone ici ? Pour le savoir on va relancer le programme (on enlève le chgt d'adresse + anti-debugger) et on met un point d'arrêt sur 4b3d20: bpm 4b3d20 x . Normallement softice apparait là :
015F:004B3D1F
C3
RET
015F:004B3D20
55
PUSH EBP
015F:004B3D21
8BEC
MOV EBP,ESP
015F:004B3D23
8B550C
MOV EDX,[EBP+0C]
015F:004B3D26
8B4508
MOV EAX,[EBP+08]
015F:004B3D29 3B0584B64B00
CMP
EAX,[004BB684] <-- eax == -1
?
015F:004B3D2F
7509
JNZ
004B3D3A
<-- non on saute
015F:004B3D31 8B0495E0B64B00
MOV EAX,[EDX*4+004BB6E0] <--
oui on met 4b3da8 dans eax
015F:004B3D38
EB07
JMP
004B3D41
<-- et on retourne dans le prog
015F:004B3D3A
52
PUSH EDX
015F:004B3D3B
50
PUSH EAX
015F:004B3D3C
E83739FFFF
CALL KERNEL32!GetProcAddress
<-- sinon on récupère l'adresse qu'il veut
015F:004B3D41
5D
POP EBP
015F:004B3D42
C20800
RET
0008
<-- et on retourne dans le programme
015F:004B3D45
8D4000
LEA EAX,[EAX+00]
015F:004B3D48
55
PUSH EBP
Intérressant non ? Donc dans notre call fixer il va falloir tenir compte du
fait que eax peut être égal à 4b3d20. Le programme fait appel plusieurs fois
à cette routine mais il ne met qu'une seule fois 4b3da8 dans eax. De plus En
testant le programme, à aucun moment le programme n'a fait appel a ce qu'il y a
en 4b3da8 (ca sert peut-être à décrypté une région du programme, mais la
une clef est nécessaire, enfin je ne sait à quoi sert cette partie). Ensuite
dans notre dump il faudra également remettre cette routine, et vu que le prog
ne c'est pas servi de ce qu'il y avait en 4b3da8 je ne l'ai pas remis (de toutes
facon ca aurait été beaucoup plus compliqué, mais je pense qu'il s'en sert
pour décrypté une partie du programme si on a une clef).
Bon maintenant on peut refaire le call fixer pour qu'il tienne compte de cela :
015F:004B6500
E80BEAFFFF
CALL
004B4F10
<-- on remet le call
015F:004B6505 81F900000040
CMP
ECX,40000000 <--
ecx contient une adresse d'api
015F:004B650B
760F
JBE
004B651C
<-- non on saute
015F:004B650D
03C8
ADD
ECX,EAX
<-- oui on calcule l'adresse
015F:004B650F
83C105
ADD ECX,05
015F:004B6512
890F
MOV
[EDI],ECX
<-- et on la met dans la table d'import
015F:004B6514
83C704
ADD
EDI,04
<-- on augment edi pour l'import suivant
015F:004B6517
E91EECFFFF
JMP
004B513A
<-- et on boucle
015F:004B651C
3D00000040
CMP
EAX,40000000 <--
eax contient une adresse d'api
015F:004B6521
7606
JBE
004B6529
<-- non on saute
015F:004B6523
AB
STOSD
<-- oui on copi l'adresse dans l'import table
015F:004B6524
E911ECFFFF
JMP
004B513A
<-- et on boucle
015F:004B6529
3D203D4B00
CMP
EAX,004B3D20 <--
eax = 4b3d20
015F:004B652E
7506
JNZ
004B6536
<-- non on saute
015F:004B6530
AB
STOSD
<-- oui on le copi dans l'import table
015F:004B6531
E904ECFFFF
JMP
004B513A
<-- et on boucle
015F:004B6536
83F900
CMP
ECX,00
<-- ecx = 0
015F:004B6539
750E
JNZ
004B6549
<-- non on saute
015F:004B653B
FF7001
PUSH DWORD PTR [EAX+01]
<-- oui on met sur la pile la ref à
l'adresse de l'api
015F:004B653E
E8F1E8FFFF
CALL
004B4E34
<-- et on appel le call de décryptage
015F:004B6543
AB
STOSD
<-- puis on copi eax dans la table d'import
015F:004B6544
E9F1EBFFFF
JMP
004B513A
<-- et on boucle
015F:004B6549
B8AC6DF7BF
MOV
EAX,KERNEL32!GetProcAddress <--
sinon on met l'adresse
015F:004B654E
AB
STOSD
<-- de getprocaddress dans la table
d'import
015F:004B654F
E9E6EBFFFF
JMP
004B513A
<-- et on boucle
015F:004B6554
0000
ADD [EAX],AL
015F:004B6556
0000
ADD [EAX],AL
Voilà le call fixer qui va mettre directement les adresses des apis dans la table d'import. Mais pour s'en servir il faut faire quelquechose sur le call 4b4e34 que l'on a trouvé précedement et qui sert à décrypter ces adresses :
004B4E34 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E
¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
004B4E34
004B4E34 ; Attributes: bp-based frame
004B4E34
004B4E34 sub_4B4E34 proc
near
; CODE XREF: .import:00DE29FDp
004B4E34
; .import:00DE2A19p ...
004B4E34
004B4E34 var_108 = byte ptr
-108h
004B4E34 var_8 =
dword ptr -8
004B4E34 var_4 =
dword ptr -4
004B4E34 arg_0 =
dword ptr 8
004B4E34
004B4E34
push ebp
004B4E35
mov ebp, esp
004B4E37
add esp, 0FFFFFEF8h
004B4E3D
push ebx
004B4E3E
push esi
004B4E3F
mov ebx, [ebp+arg_0]
004B4E42
mov eax, [ebx]
004B4E44
mov [ebp+var_8], eax
004B4E47
lea eax, [ebp+var_108]
004B4E4D
xor ecx, ecx
004B4E4F
mov edx, 100h
004B4E54
call sub_4A5880
004B4E59
mov eax, ebx
004B4E5B
mov edx, [eax+4]
004B4E5E
mov dl, [edx]
004B4E60
cmp dl, ds:byte_4BB74C <-- je sais
c'est mal désassemblé mais bon ...
004B4E66
jz short loc_4B4EB5
004B4E68
mov edx, [eax+4]
004B4E6B
mov ecx, edx
004B4E6D
inc ecx
004B4E6E
mov bl, [ecx]
004B4E70
add edx, 2
004B4E73
mov [ebp+var_4], edx
004B4E76
mov esi, ebx
004B4E78
and esi, 0FFh
004B4E7E
mov ecx, esi
004B4E80
lea eax, [ebp+var_108]
004B4E86
mov edx, [ebp+var_4]
004B4E89
call sub_4A76F0
004B4E8E
mov eax, [ebp+var_8]
004B4E91
call sub_4A817C
004B4E96
sub eax, 2
004B4E99
push eax
004B4E9A
mov edx, esi
004B4E9C
lea eax, [ebp+var_108]
004B4EA2
mov ecx, [ebp+var_8]
004B4EA5
call sub_4B3564
004B4EAA
lea eax, [ebp+var_108]
004B4EB0
mov [ebp+var_4], eax
004B4EB3
jmp short loc_4B4EBE
004B4EB5 ;
---------------------------------------------------------------------------
004B4EB5
004B4EB5
loc_4B4EB5:
; CODE XREF: sub_4B4E34+32j
004B4EB5
mov eax, [eax+4]
004B4EB8
inc eax
004B4EB9
mov eax, [eax]
004B4EBB
mov [ebp+var_4], eax
004B4EBE
004B4EBE
loc_4B4EBE:
; CODE XREF: sub_4B4E34+7Fj
004B4EBE
push [ebp+var_4]
004B4EC1
push [ebp+var_8]
004B4EC4
call sub_4B4C8C
004B4EC9
mov [ebp+4],
eax <-- il faut nopper cette
instruction
004B4ECC
pop esi
004B4ECD
pop ebx
004B4ECE
mov esp, ebp
004B4ED0
pop ebp
004B4ED1
retn 4
004B4ED1 sub_4B4E34 endp
004B4ED1
004B4ED4
Il faut nopper l'instruction en 4b4ec9 sinon on aura droit à une erreur 13
et le prog s'arrêtera. Voilà maintenant on continu de tracer le programme (on
laisse notre "call fixer" décrypté les adresses) et on va jusqu'à
l'endroit où le programme passe la main à celui décompressé en mémoire. On
retrouve le call suivi de la sauvegarde des registre, donc on rentre dedans.
Mais ensuite on trouve presque tout de suite le popad. Là on note le nouvel eip
: 421bfc, et on fait a eip et on met jmp eip. On quitte softice (f5) et on dump
la cible avec procdump. Attention utilisez l'option don't rebuild import table
ou use actual import, pour le dump.
Maintenant comme pour pleditor il faut changer l'eip avec le nouveau soit 21bfc
(421bfc-400000). Il faut également copié la section .aspr (ici c'est l'avant
dernière section la .data) de l'original sur celle du dump. Ensuite on édite
avec un éditeur hexa ce qu'il y a en 4290c8. Et oui il y a la référence en
4b3d20, donc on va mettre une référence vers une zone libre du programme.
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00029090 0D 0B F8 BF 56 F6 F9 BF 15 0A F8 BF 24 2E F9
BF ..ø¿Vöù¿..ø¿$.ù¿
000290A0 C4 64 F7 BF 93 7B F7 BF AD 73 F7 BF 2B 0B FA
BF Äd÷¿“{÷¿s÷¿+.ú¿
000290B0 D5 6F F7 BF B4 5C F9 BF 2C 62 F9 BF FE D5 F9
BF Õo÷¿´\ù¿,bù¿þÕù¿
000290C0 B4 20 F8 BF EF 62 F9 BF 00 70 47 00 A0 E0
F8 BF ´ ø¿ïbù¿..w. àø¿
000290D0 B3 CA F8 BF 85 7D F7 BF 65 43 F7 BF 3C 43 F7
BF ³Êø¿…}÷¿eC÷¿<C÷¿
000290E0 B8 48 F7 BF DB 6D F7 BF 1F 6E F7 BF 41 6E F7
BF ¸H÷¿Ûm÷¿.n÷¿An÷¿
J'ai trouvé de la place libre en 477000. Donc il va falloir rajouté la routine qui se trouvait normalement en 4b3d20 :
015F:00477000
55
PUSH EBP
015F:00477001
8BEC
MOV EBP,ESP
015F:00477003
8B550C
MOV EDX,[EBP+0C]
015F:00477006
8B4508
MOV EAX,[EBP+08]
015F:00477009
3DFFFFFFFF
CMP EAX,FFFFFFFF
015F:0047700E
90
NOP
015F:0047700F
7509
JNZ 0047701A
015F:00477011
B8A83D4B00
MOV EAX,004B3DA8
015F:00477016
90
NOP
015F:00477017
90
NOP
015F:00477018
EB07
JMP 00477021
015F:0047701A
52
PUSH EDX
015F:0047701B
50
PUSH EAX
015F:0047701C
E88BFDAFBF
CALL KERNEL32!GetProcAddress
015F:00477021
5D
POP EBP
015F:00477022
C20800
RET 0008
Voilà normalement maintenant le dump est complètement fonctionnel :). Il ne
reste plus qu'à le cracké...
Enfin il existe une méthode plus simple pour obtenir les addresses: vous devez
d'abord dumpé le programme. Ensuite l'exe d'origine doit être lancé et vous
executé le programme import_list
(désolé j'ai oublié le nom de l'auteur) qui vous donnera un fichier avec les
bonnes adresses, il ne reste plus qu'à faire du copier/coller (vous aurez même
un txt ave les changements :) ). Enfin sur ce programme il y a toujours le
problème du 4b3d20 à régler ...
En fait je n'est pas pris le meilleur programme pour vous montrer cette exemple. En effet si vous relancé votre machine il y a peu de chance que le dump fonctionne, pourquoi ? et bien parce que certaine librairie de windows se charge à des endroits différents en mémoires. Voici ou se trouve le problème pour ce dump :
00029000 AA19 EABF 4416 EABF 3415 EABF D114 EABF ....D...4.......
00029010 EA15 EABF 7D16 EABF 2B18 EABF 8717 EABF ....}...+.......
00029020 2A17 EABF 0000 0000 97D3 7282 FE43 7082 *.........r..Cp.
00029030 F205 7582 F4DB 7182 77D8 7182 FD4E 7282 ..u...q.w.q..Nr.
00029040 0000 0000 4228 F2BF FE24 F2BF D824 F2BF ....B(...$...$..
00029050 3A28 F2BF 8A28 F2BF 9322 F2BF 3551 F2BF :(...(..."..5Q..
00029060 144A F2BF 7B50 F2BF 8D14 F2BF 7F26 F2BF .J..{P.......&..
00029070 0000 0000 DF7A F7BF E250 F8BF A570 F7BF .....z...P...p..
Il y a 6 adresses sélectionnées et voici à quoi elle correspondent :
00DE2B48->8272D397[2]=(00001028)COMCTL32.DLL!CreateToolbarEx:0017
00DE2B54->827043FE[2]=(0000102C)COMCTL32.DLL!InitCommonControls:0011
00DE2B60->827505F2[2]=(00001030)COMCTL32.DLL!CreateStatusWindow:0015
00DE2B6C->8271DBF4[2]=(00001034)COMCTL32.DLL!ImageList_ReplaceIcon:0046
00DE2B78->8271D877[2]=(00001038)COMCTL32.DLL!ImageList_Create:002D
00DE2B84->82724EFD[2]=(0000103C)COMCTL32.DLL!PropertySheet:0056
Donc pour ces 6 adresses il va falloir faire une modification. Je vais
simplement vous donner le principe car la méthode qui suit fait pareil mais en
nettement mieux (enfin la méthode de Tsehp celle qui suit a changé et n'y fait
pas référence). Ces 6 adresses nous savons à quelles fonctions elles
correspondent, donc le principe est de modifé l'eip du prog vers un endroit où
on peut mettre notre code . Là il faut avoir préalablement noté le nom de la
dll et des fonctions utilisées. Encuite en utilisant GetModuleHandle puis
GetProcAddress on répurère l'adresse mémoire et on va l'écrire à son
emplacement dans la section .rdata. Comme cela il n'y aura plus de problème
d'adresse pour ces fonctions. Je n'est volontairement pas expliqué
complétement cette partie car entre temps Tsehp a mis au point une nouvelle
méthode de dump ... et dans celle ci vous comprendrez de quoi je parles ...
IV_3ème méthode: amv vu différement |
Maintenant je vais essayer de décrire une méthode qui permet d'obtenir un
dump qui marche sur toutes les machines (enfin sur toutes les version de win 9x
si le dump est fait sous win 9x et sous les version de NT/2000 si le dump ...
enfin voilà :) ). Je vais vous présenté une méthode qui a été mise
au point par Tsehp (very good work man): vous pouver trouver son essai original
sur Commview à cette adresse : http://tsehp.cjb.net
en allant dans la section news, c'est l'essai sur asprotect1.05 . Je vous la
présente car le début de sa méthode (comment obtenir les noms m'a permis
d'élaborer ensuite la mienne).
Je vais essayer de vous expliquer cela clairement mais ca vas être difficile,
car la méthode par elle même n'est pas simple (enfin quand on sait le faire ca
devient plus simple :)). Tout d'abord le principe est le suivant : au lieu de
faire un dump qui a les adresses vers les fonctions utilisées déjà ecrites
dans la section .rdata (dans le cas de amv sinon ca peut-être la .idata comme
pleditor) il faudrait plutôt que l'on récupère le nom de cette fonction
(comme CreateFileA) leur module (comme kernel32.dll) et l'endroit où elle
doivent être dans la section .rdata (ou .idata). Comme cela dans le dump il
suffirait de dévier le début du programme vers un bout de code qui se
chargerait de récupérer l'adresse de ces fonctions et de les écrires à leurs
places dans la section .rdata, comme cela le dump marcherai sur toutes les
versions de win9x (ou nt/2000 si le dump est fait avec).
Où pourrait-on faire ca? Et bien au même endroit que précédement, au niveau
du call fixer. Pourquoi là? et bien c'est très simple : tout d'abord on a
chaque adresse vers la section .rdata où il écrit. Mais en plus pour faire ces
liens il est bien obligés de connaitre de quelles fonctions il s'agit (en fait
c'est plus compliqué que cela).
Bon je ne vous réexplique pas comment arrivé là, lisez la partie précédente :
015F:004B5138
87FE
XCHG EDI,ESI
015F:004B513A
AC
LODSB
015F:004B513B
08C0
OR AL,AL
015F:004B513D
74E4
JZ 004B5123
015F:004B513F
4E
DEC ESI
015F:004B5140
56
PUSH ESI
015F:004B5141
53
PUSH EBX
015F:004B5142
80F802
CMP AL,02
015F:004B5145
7407
JZ 004B514E
015F:004B5147
0FB64E01
MOVZX ECX,BYTE PTR [ESI+01]
015F:004B514B
41
INC ECX
015F:004B514C
EB05
JMP 004B5153
015F:004B514E
B904000000
MOV ECX,00000004
015F:004B5153
41
INC ECX
015F:004B5154
01CE
ADD ESI,ECX
015F:004B5156
E8B5FDFFFF
CALL
004B4F10
<-- cherche les apis
015F:004B515B
AB
STOSD
015F:004B515C
EBDC
JMP
004B513A
<-- retourne au début
015F:004B515E
61
POPAD
Au niveau du call 4b4f10, on remarque que edi contient l'adresse de destination dans la section .rdata et certaine fois eax ou edx on l'adresse d'une fonction. Il serait donc intérressant de regarder un peu ce call 4b4f10. Je ne vais pas l'analyser, je vous conseil plutôt de lire l'essai de TeeJi qui a fait cela très bien. Voici qd même une petite partie :
004B4F10 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E
¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
004B4F10
004B4F10 ; Attributes: bp-based frame
004B4F10
004B4F10 sub_4B4F10 proc
near
; CODE XREF: sub_4B5094+C2p
004B4F10
004B4F10 var_11D = byte ptr
-11Dh
004B4F10 var_1D = byte
ptr -1Dh
004B4F10 var_1C =
dword ptr -1Ch
004B4F10 var_18 = byte
ptr -18h
004B4F10 var_17 =
dword ptr -17h
004B4F10 var_13 = byte
ptr -13h
004B4F10 var_12 =
dword ptr -12h
004B4F10 var_E =
dword ptr -0Eh
004B4F10 var_A =
dword ptr -0Ah
004B4F10 arg_0 =
dword ptr 8
004B4F10 arg_4 =
dword ptr 0Ch
004B4F10
004B4F10
push ebp
004B4F11
mov ebp, esp
004B4F13
add esp, 0FFFFFEE0h
004B4F19
push ebx
004B4F1A
push esi
004B4F1B
push edi
004B4F1C
mov edi, [ebp+arg_4]
004B4F1F
mov esi, [ebp+arg_0]
004B4F22
mov bl, [esi]
004B4F24
lea eax,
[ebp+var_11D] <-- notez
l'adresse ebp-11d : pour moi 6afc6b
004B4F2A
xor ecx, ecx
004B4F2C
mov edx, 100h
004B4F31
call sub_4A5880
004B4F36
sub bl, 2
004B4F39
jz short loc_4B4F49
004B4F3B
sub bl, 2
004B4F3E
jz loc_4B4FE1
004B4F44
jmp loc_4B5035
004B4F49
............................................................................. <-- j'ai coupé
004B508A
004B508A
loc_4B508A:
; CODE XREF: sub_4B4F10+9Aj
004B508A
; sub_4B4F10+B9j ...
004B508A
pop edi
004B508B
pop esi
004B508C
pop ebx
004B508D
mov esp, ebp
004B508F
pop ebp
004B5090
retn 8
004B5090 sub_4B4F10 endp
004B5090
Bon là j'ai juste mis le début du call et la fin (il est long), mais en tracant et en ayant repéré l'adresse indiqué vous verez apparaitre le nom de la fonction (la première c'est CreateFileA). Et on resort du call (pour comprendre son utilité voir l'essai de TeeJi): là le nom de la fonction est toujours en 6afc6b. On peut repérer cette adresse à la sortit du call en faisant ebp-15dh mais ceci reste encore à vérifié. Donc obtient donc bien le nom de la fonction désirées, de plus le nom de la dll associé apparait en faisant d ebx, et dans edi on a l'adresse de destination dans la section .rdata. Donc on a tout, sauf pour les fonctions cryptées alors là comme dans la partie précédente on fait appel à la fonction de décryptage (voir la partie précédente pour savoir comment la localiser) et on obtient le nom de la fonction. Il ne reste plus qu'un cas a traité c'est le cas où les fonctions sont appelées par leurs ordinal ... et bien vous verez que contrairement à Tsehp je ne m'en préoccupe pas ... vous comprendrez pourquoi.
Bon maintenant attaquons nous à la bête :). Donc en 4b5156 on dévit le call en mettant un jmp 4b6500 (voir partie 3), où on a de la place libre. Ensuite il faut trouver un espace mémoire libre pour pouvoir aller écrire le noms des fonctions, des dlls et leur adresses dans la section .rdata. Pour ma part je rajoute le début de code suivant en 4b6500 que j'enlève après pour mettre le call fixer :
pushad
<-- sauve les registres
push
40
<-- rempli avec des 00
push
00003000
<-- readable writable and executable
push
00008000
<-- taille de la mémoire allouée (on prend ses précautions)
push
01000000
<-- adresse où l'on veut allouée de la mémoire (j'ai mis 1000000 car il
n'y avait rien mais ceci est aléatoire)
call
KERNEL32!VirtualAlloc
<-- on alloue la mémoire
popad
<-- on restaure les registres
Maintenant que l'on a notre zone alloué en 1000000 et se remet au début de notre code en 4b6500 (r eip 4b6500). Maintenant là on place le call fixer qui va se charger de copier le noms des fonctions avec dlls et adresses de la section .rdata en 1000000. Voici le call fixer de Tsehp légèrement modifé :
015F:004B6500
E80BEAFFFF
CALL
004B4F10
<-- on restaure le call
015F:004B6505
60
PUSHAD
<-- on sauve les registres
015F:004B6506 833D6BFC6A0000
CMP DWORD PTR
[006AFC6B],00 <-- on regarde s'il y a un
nom de fonction
015F:004B650D
7456
JZ
004B6565
<-- si il n'y en a pas alors on va vers la
décrytion / ordinal
015F:004B650F
6855FE6A00
PUSH
006AFE55
<-- on met le nom de la foncttion
015F:004B6514 FF35F0644B00
PUSH DWORD PTR
[004B64F0] <-- l'adresse ou
l'on veut copier (avant
d'éxécuter la boucle pensé à mettre
00000001 en 4b64f0
015F:004B651A
E8DD0DACBF
CALL
KERNEL32!lstrcpy
<-- on copie
015F:004B651F FF35F0644B00
PUSH DWORD PTR
[004B64F0] <-- on met
l'adresse ou se trouve le nom
015F:004B6525
E8830EACBF
CALL
KERNEL32!lstrlen
<-- on calcul sa longueur
015F:004B652A
40
INC
EAX
<-- pour rajouté un 0
015F:004B652B
43
INC
EBX
<-- pour être au début du nom de la dll
015F:004B652C 0105F0644B00
ADD
[004B64F0],EAX
<-- on augmente l'adresse de destination
015F:004B6532
53
PUSH
EBX
<-- on met le nom de la dll
015F:004B6533 FF35F0644B00
PUSH DWORD PTR
[004B64F0] <--
l'adresse de destination
015F:004B6539
E8BE0DACBF
CALL
KERNEL32!lstrcpy
<-- on copie
015F:004B653E FF35F0644B00
PUSH DWORD PTR
[004B64F0] <-- on met
l'adresse ou se trouve le nom
015F:004B6544
E8640EACBF
CALL
KERNEL32!lstrlen
<-- on calcul sa longueur
015F:004B6549
40
INC
EAX
<-- pour rajouté un 0
015F:004B654A 0105F0644B00
ADD
[004B64F0],EAX
<-- on augmente l'adresse de destination
015F:004B6550 8B0DF0644B00
MOV
ECX,[004B64F0]
<-- on met l'adresse dans ecx
015F:004B6556
8939
MOV
[ECX],EDI
<-- et on y copie l'adresse de .rdata qui
correspond à la fonction
015F:004B6558 8305F0644B0005
ADD DWORD PTR
[004B64F0],05 <-- on augmente l'adresse
de destination
015F:004B655F
61
POPAD
<-- on restaure les registres
015F:004B6560
E9D5EBFFFF
JMP
004B513A
<-- et on retourne dans la boucle d'asprotect
au niveau du stosd
015F:004B6565
3D00000040
CMP
EAX,40000000
<-- eax contient l'adresse d'une api
015F:004B656A
7D14
JGE
004B6580
<-- oui alors on va vers le traitement par
ordinal
015F:004B656C
83F900
CMP
ECX,00
<-- ecx == 0
015F:004B656F
750F
JNZ
004B6580
<-- oui alors ordinal, sinon crypté
015F:004B6571
FF7001
PUSH DWORD PTR
[EAX+01]
<-- on met le paramêtre à la proc de
décryption
015F:004B6574
E8BBE8FFFF
CALL
004B4E34
<-- on décrypte (il faut mettre trois nop en
4b4e34+95 pour éviter les pb
015F:004B6579
686BFC6A00
PUSH
006AFC6B
<-- on met l'adresse du nom
015F:004B657E
EB94
JMP
004B6514
<-- et on retourne à la copi
015F:004B6580
83EE04
SUB
ESI,04
<-- ordinal alors on enlève 4 à esi
015F:004B6583
56
PUSH
ESI
<-- on le met sur la pile
015F:004B6584
EB8E
JMP
004B6514
<-- et on retourne à la copie
015F:004B6586
0000
ADD [EAX],AL
015F:004B6588
0000
ADD [EAX],AL
Voilà je pense que cette fonction est assez bien détaillée. Cependant il
faut mettre un bpm 4b6579 après le call de décryption pour vérifié si le nom
de la proc est toujours à la même adresse. Si elle ne l'est pas et bien elle
n'est pas très loin (un petit coup d'oeil sous softice) et on remplace
l'adresse et après on peut boucler tranquilement. (note : un point d'arrêt
après le call que l'on a dévié fait passer la boucle).
Une fois que la boucle est finie il faut copier tout ce qui est en 1000000 avec
icedump, pagein d 1000000 2000 c:\noms.dat. Ensuite Tsehp propose de rajouté
une section au dump et d'y mettre les noms (avec les adresses) puis d'écrire
une procédure qui se chargerait de récupérer chaque nom (ou ordinal) et de
récupérer l'adresse de la fonction avec getmodulehandle et getprocaddress
(voir son tut), puis de mettre cette adresse en .rdata correspondand. Comme cela
on aurait nous aussi reconstruit une fausse table d'import et le programme
marcherait.
Mais là description de sa méthode s'arrête là. En effet j'ai changé tout ca de manière non pas à obtenir une fausse table d'import pour que le programme marche mais une vraie comme si on n'avait jamais touvhé au programme (aucun rajout de section ou de code après). Ceci est possible car pour le moment asprotect ne détruit pas encore tout ... enfin sur la version d'amv, vous verez après que la dernière version (pleditor, commview ...) est plus compliqué .... Enfin repartons du début :
015F:004B5138
87FE
XCHG EDI,ESI
015F:004B513A
AC
LODSB
<-- on est là
015F:004B513B
08C0
OR AL,AL
015F:004B513D
74E4
JZ 004B5123
015F:004B513F
4E
DEC ESI
015F:004B5140
56
PUSH ESI
015F:004B5141
53
PUSH EBX
015F:004B5142
80F802
CMP AL,02
015F:004B5145
7407
JZ 004B514E
015F:004B5147
0FB64E01
MOVZX ECX,BYTE PTR [ESI+01]
015F:004B514B
41
INC ECX
015F:004B514C
EB05
JMP 004B5153
015F:004B514E
B904000000
MOV ECX,00000004
015F:004B5153
41
INC ECX
015F:004B5154
01CE
ADD ESI,ECX
015F:004B5156
E8B5FDFFFF
CALL
004B4F10
<-- cherche les apis
015F:004B515B
AB
STOSD
015F:004B515C
EBDC
JMP
004B513A
<-- retourne au début
015F:004B515E
61
POPAD
Donc on se trouve en 4b513a et rien n'a encore été fait. Et bien regardons comment est le début de la section .rdata a ce moment là :
0030:00429000 72 B3 02 00 FC B2 02 00-0A B3 02 00 1E B3 02
00 r...............
0030:00429010 2E B3 02 00 40 B3 02 00-52 B3 02 00 62 B3 02 00
....@...R...b...
0030:00429020 80 B3 02 00 00 00 00 00-2C B4 02 00 11 00 00 80
........,.......
0030:00429030 06 00 00 80 3E B4 02 00-56 B4 02 00 6A B4 02 00
....>...V...j...
0030:00429040 00 00 00 00 12 B2 02 00-44 B2 02 00 54 B2 02 00
........D...T...
0030:00429050 38 B2 02 00 7A B2 02 00-86 B2 02 00 6A B2 02 00
8...z.......j...
0030:00429060 9E B2 02 00 AC B2 02 00-90 B2 02 00 22 B2 02 00
............"...
0030:00429070 00 00 00 00 58 AB 02 00-66 AB 02 00 76 AB 02 00
....X...f...v...
0030:00429080 4C AB 02 00 3E AB 02 00-88 AB 02 00 A8 AB 02 00
L...>...........
0030:00429090 B8 AB 02 00 C8 AB 02 00-DA AB 02 00 98 AB 02 00
................
0030:004290A0 F6 AB 02 00 02 AC 02 00-EA AB 02 00 24 AC 02 00
............$...
0030:004290B0 3E AC 02 00 4A AC 02 00-5E AC 02 00 72 AC 02 00
>...J...^...r...
Et oui on trouve des références du style 2b372, or si on ajoute l'image base on a 42b372 ... est ce que ceci ne ressemblerait pas à une import address table (lien vers les noms des fonctions en gros)? et bien oui, asprotect n'a pas détrui l'iat elle est comme neuve. Regardons un peu plus loin :
0030:00429300 E2 AF 02 00 CE AF 02 00-C2 AF 02 00 B4 AF 02 00
................
0030:00429310 A0 AF 02 00 92 AF 02 00-84 AF 02 00 76 AF 02 00
............v...
0030:00429320 60 AF 02 00 4A AF 02 00-36 AF 02 00 26 AF 02 00
`...J...6...&...
0030:00429330 12 AE 02 00 04 AE 02 00-8C B0 02 00 A6 B1 02 00
................
0030:00429340 00 00 00 00 D2 B3 02 00-E4 B3 02 00 FA B3 02 00
................
0030:00429350 00 00 00 00 14 00 00 80-03 00 00 80 13 00 00 80
................
0030:00429360 10 00 00 80 02 00 00 80-12 00 00 80 16 00 00 80
................
0030:00429370 06 00 00 80 74 00 00 80-73 00 00 80 33 00 00 80
....t...s...3...
0030:00429380 0A 00 00 80 11 00 00 80-04 00 00 80 17 00 00 80
................
0030:00429390 34 00 00 80 00 00 00 00-C6 B2 02 00 DA B2 02 00
4...............
0030:004293A0 00 00 00 00 00 00 00 00-53 6F 66 74 77 61 72 65
........Software
0030:004293B0 5C 4D 69 63 72 6F 73 6F-66 74 5C 57 41 42 5C 44
\Microsoft\WAB\D
Pourquoi j'ai mis 14 00 00 80 en plus gros ? et bien simplement parceque là aussi c'est l'iat mais ce n'est pas un lien vers le nom de la fonction -2, c'est l'ordinal de la fonction utilisé. Bon maintenant allons voir la suite : en 429000 on a 2b372, donc normallement en 42b372 on devrait avoir l'import table (celle avec les noms et l'ordinal de chaque fonctions) :
0030:0042B2F0 00 00 00 00 00 00 00 00-00 00 00 00 5B 01 00 00
............[...
0030:0042B300 00 00 00 00 00 00 00 00-00 00 7B 01 00 00 00 00
..........{.....
0030:0042B310 00 00 00 00 00 00 00 00-00 00 00 00 00 00 72 01
..............r.
0030:0042B320 00 00 00 00 00 00 00 00-00 00 00 00 00 00 86 01
................
0030:0042B330 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
................
0030:0042B340 5F 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00
_...............
0030:0042B350 00 00 67 01 00 00 00 00-00 00 00 00 00 00 00 00
..g.............
0030:0042B360 00 00 62 01 00 00 00 00-00 00 00 00 00 00 00 00
..b.............
0030:0042B370 00 00 71 01 00 00 00 00-00 00 00 00 00 00 00 00
..q.............
0030:0042B380 5E 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00
^...............
0030:0042B390 00 00 00 00 00 00 00 00-00 00 00 00 00 00 72 00
..............r.
0030:0042B3A0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
................
Et là il n'y a pas de nom de fonction :(. Mais en 42b372 on a pourtant 7101
puis des 00 00. Et bien le 7101 c'est l'ordinal de la fonction et juste après
on devrait avoir le nom de la fonction. Car l'it est fait comme cela : un word
pour l'ordinal de la fonction et juste après le nom de la fonction. Donc en
fait dans l'iat les références pointe vers le nom de la fonction-2.
C'est interressant tout cela on a l'iat correct et l'it (supposé) contient des
espaces vides avec des words suposés être l'ordinal de la fonction. Il faut
donc le vérifier. On va faire un premier passage dans la boucle jusqu'au call
inclu. Là si on regarde en 6afc3b on a la fonction CreateFileA et en edi
429074. Donc on va voir en 429074 et on a 58 AB 02 00 , donc si on ajoute
l'image base (400000 pour amv) et bien on obtient 42ab58. Allons voir en 42ab58
:
0042AB58 3400 0000 0000 0000 0000 0000 0000 1A01 4...............
Et bien en 42AB58 on a 3400 (lordinal de createfilea) et juste la place de
mettre CreateFileA (avec un 0 caractère de fin) avant d'arriver sur l'ordinal
suivant. Donc c'est bien cela : c'est l'it sans les noms.
Donc l'idée est de faire un call fixer qui va se charger de remettre les noms
à leurs places. Et oui comme edi pointe vers ce qu'il y a en 429xxx, il suffit
de récupérer ce qu'il y a et de rajouter l'image base comme cela on sera au
début de l'endroit où doit être le nom de la fonction-2. Pour ce qui est du
cas des fonctions appelés par ordinal il n'y a rien à faire car l'ordinal est
déjà dans l'iat. Ensuite on dumpera cette partie que l'on remettre dans le
dump.
Bon maintenant il faut le faire :). Donc on recommence tout et on est de nouveau au début de la boucle en 4b513a. Là on va jusqu'au call qu'on dévit de nouveau :
015F:004B5147
0FB64E01
MOVZX ECX,BYTE PTR [ESI+01]
015F:004B514B
41
INC ECX
015F:004B514C
EB05
JMP 004B5153
015F:004B514E
B904000000
MOV ECX,00000004
015F:004B5153
41
INC ECX
015F:004B5154
01CE
ADD ESI,ECX
015F:004B5156
E9A5130000
JMP
004B6500 <-- on dévit
015F:004B515B
AB
STOSD
015F:004B515C
EBDC
JMP 004B513A
015F:004B515E
61
POPAD
Maintenant on peut mettre en 4b6500 notre fixer :
015F:004B6500
E80BEAFFFF
CALL
004B4F10
<-- le call que l'on a détruit
015F:004B6505
60
PUSHAD
<-- on sauve les registres
015F:004B6506 833D6BFC6A0000
CMP DWORD PTR
[006AFC6B],00 <-- est que le nom est là si non
on saute
015F:004B650D
741C
JZ
004B652B
<-- et on va vers crypté/ordinal
015F:004B650F
686BFC6A00
PUSH
006AFC6B
<-- on met le nom
015F:004B6514
8B0F
MOV
ECX,[EDI]
<-- on met la VA pointé par edi dans .rdata
015F:004B6516 81C102004000
ADD
ECX,00400002
<-- on rajoute l'imagebase + 2 pour passer
l'ordinal
015F:004B651C
51
PUSH
ECX
<-- on met l'adresse de destination
015F:004B651D
E8DA0DACBF
CALL
KERNEL32!lstrcpy
<-- et on copie
015F:004B6522
61
POPAD
<-- on restaure les registres
015F:004B6523
83C704
ADD
EDI,04
<-- on passe au prochain traitement
015F:004B6526
E90FECFFFF
JMP
004B513A
<-- et on boucle
015F:004B652B
3D00000040
CMP
EAX,40000000
<-- eax contient l'adresse d'une api
015F:004B6530
7DF0
JGE
004B6522
<-- oui alors c'est par ordinal et on
passe au suivant car l'ordinal est déjà
dans l'iat
015F:004B6532
83F900
CMP
ECX,00
<-- ecx = 0 si non alors ordinal et on
015F:004B6535
75EB
JNZ
004B6522
<-- passe au suivant
015F:004B6537
FF7001
PUSH DWORD PTR
[EAX+01] <--
sinon c crypté et on passe le paramètre
015F:004B653A
E8F5E8FFFF
CALL
004B4E34
<-- à la fonction de décryption
015F:004B653F
6864FC6A00
PUSH
006AFC64
<-- on recherche l'adresse du nom que l'on
met sur la pile
015F:004B6544
EBCE
JMP 004B6514
<-- et on va l'écrire dans l'it
Voilà cette proc est bien commentée je pense. Toutefois il faut également
mettre 3 nops en 4b4e34+95 (la proc de décryption) et faire attention à
l'adresse du nom en retour de cette procédure (voir partie précédente).
Avant de laisser tournée la boucle il faut mettre un bpm 4b515e x pour
s'arrêter après cette boucle. Une fois qu'elle est finie il faut dumper toute
la partie qui a changé : pagein d 429000 2c00 c:\import.dat .
Maintenant on prend le dump du prog (obtenu en allant à l'endroit où il
passe la main à celui décompressé et en ayant mis jmp eip, puis en le dumpant
avec procdump ou procinfos). Si jamais vous avez dumper le prog avec icedump
pensé à le reconstruire avec perebuilder (avec l'option fix raw offset) ou
alors vous mettez directement le raw offset de chaque section égal à la
virtual address .
Maintenant une chose très pratique est que la virtual adress est égal au raw
ofset, comme cela on se repère facilement. Il va donc faloir coller l'import
que l'on a dumpé dans le dump en 29000. Une fois cela fait le dump n'est pas
encore fonctionnel : en effet on a bien l'it (les noms des fonctions avec
l'ordinal), l'iat (liens vers le noms des fonctions) mais il manque encore
l'import descriptor qui dit quelles dlls sont utilisées.
Et bien il nous reste encore à le reconstruire.
Pour ce faire un petit coup d'oeil sur le tut de TeeJi sur comment rajouter une dll dans l'iat et on comprend tout de suite mieux de quoi ca parle :). Bon on va commencer par le début : comment se présente d'import descriptor :
IMAGE_IMPORT_DESCRIPTOR struct
OriginalFirstThunk dd 0
;RVA to original unbound IAT
TimeDateStamp dd 0 ;not used
here
ForwarderChain dd 0 ;not used here
Name
dd 0 ;RVA to DLL name sring
FirstThunk dd
0 ;RVA to IAT array
IMAGE_IMPORT_DESCRIPTOR ends
Donc pour une dll il y a 4 dword : le premier correspond au début de la zone
de l'iat dorrespondante, le deuxième on s'en fou, le troisième ne sert pas ici
donc on met FFFFFFFF, le quatrième pointe vers le nom de la dll, et le
cinquième vers un tableau d'adresse (comme ici il n'y en a pas on le mettra
égal à l'OriginalFirstThunk).
Bon, commencons : tout d'abord allons au début de la section .rdata en 429000 :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00029000 72 B3 02 00 FC B2 02 00 0A B3 02 00 1E B3
02 00 r³..ü²...³...³..
00029010 2E B3 02 00 40 B3 02 00 52 B3 02 00 62 B3 02
00 .³..@³..R³..b³..
00029020 80 B3 02 00 00 00 00 00 2C B4 02 00 11 00
00 80 €³......,´.....€
00029030 06 00 00 80 3E B4 02 00 56 B4 02 00 6A B4 02
00 ...€>´..V´..j´..
00029040 00 00 00 00 12 B2 02 00 44 B2 02 00 54 B2 02
00 .....²..D²..T²..
00029050 38 B2 02 00 7A B2 02 00 86 B2 02 00 6A B2 02
00 8²..z²..†²..j²..
00029060 9E B2 02 00 AC B2 02 00 90 B2 02 00 22 B2 02
00 ž²..¬²..².."²..
Voilà comment on repaire les dlls utilisés: au début de la section en 29000 et bien on a des liens vers les noms-2. J'en ai sélectionné 17 car juste après il y a 00 00 00 00. Ceci permet de dire que le prog fait appel à 17 fonctions de cette dlls. On sait déjà que L'originalFirstThunk est 29000. Pour le nom de la dll on pourrait le mettre n'importe où mais on peut faire mieux ici : Le premier liens est 2b372, on y va :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0002B350 41 00 67 01 52 65 67 45 6E 75 6D 4B 65 79 45
78 A.g.RegEnumKeyEx
0002B360 41 00 62 01 52 65 67 44 65 6C 65 74 65 4B 65
79 A.b.RegDeleteKey
0002B370 41 00 71 01 52 65 67 4F 70 65 6E 4B 65 79 41
00 A.q.RegOpenKeyA.
0002B380 5E 01 52 65 67 43 72 65 61 74 65 4B 65 79 41
00 ^.RegCreateKeyA.
0002B390 00 00 00 00 00 00 00 00 00 00 00 00 00 00
72 00 ..............r.
0002B3A0 53 68 65 6C 6C 45 78 65 63 75 74 65 41 00 00
00 ShellExecuteA...
En 2b390 on trouve plein de 00, et au dessus les noms des fonctions de la dlls, pointés par la partie de l'iat sélectionné. Or c'est fonctions font partis de la dll advapi32.dll. Donc en 2b390 on met advapi32.dll et le nom de la dll est en 2b390. Si vous ne vous y retrouvé pas au niveau des noms des dlls utilisé le prog import list il vous donnera dans l'ordre les fonctions avec leur dll associée. Maintenant on passe en 29020 et on recommence avec la dll suivante . Pour ce qui est des Ordinals il se trouve que dans ce prog c'est pour la dll wsock32.dll (vous pouvez le repérer soit avec le prog russe, soit au niveau de la boucle que l'on a fait et en mettant un point d'arrêt au niveau du traitement de l'ordinal et vous notez la/les dll(s) correspondantes). Pour écrire l'import descriptor on choisi une place libre, voici le mien reconstruit :
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0002A800 00 90 02 00 00 00 00 00 FF FF FF FF 90 B3
02 00 .......ÿÿÿÿ³..
0002A810 00 90 02 00 28 90 02 00 00 00 00 00 FF FF
FF FF ...(......ÿÿÿÿ
0002A820 7B B4 02 00 28 90 02 00 44 90 02 00 00 00 00
00 {´..(..D......
0002A830 FF FF FF FF BC B2 02 00 44 90 02 00 74 90
02 00 ÿÿÿÿ¼²..D..t..
0002A840 00 00 00 00 FF FF FF FF 2F AD 02 00 74 90 02
00 ....ÿÿÿÿ/..t..
0002A850 04 92 02 00 00 00 00 00 FF FF FF FF AE B3 02
00 .’......ÿÿÿÿ®³..
0002A860 04 92 02 00 0C 92 02 00 00 00 00 00 FF FF
FF FF .’...’......ÿÿÿÿ
0002A870 05 B2 02 00 0C 92 02 00 44 93 02 00 00 00 00
00 .²...’..D“......
0002A880 FF FF FF FF 14 B4 02 00 44 93 02 00 54 93
02 00 ÿÿÿÿ.´..D“..T“..
0002A890 00 00 00 00 FF FF FF FF D0 A8 02 00 54 93 02
00 ....ÿÿÿÿШ..T“..
0002A8A0 98 93 02 00 00 00 00 00 FF FF FF FF ED B2 02
00 ˜“......ÿÿÿÿí²..
0002A8B0 98 93 02 00 00 00 00 00 00 00 00 00 00 00
00 00 ˜“..............
0002A8C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 ................
0002A8D0 57 53 4F 43 4B 33 32 2E 44 4C 4C 00 00 00 00
00 WSOCK32.DLL.....
.
Voilà, j'ai mis en evidente les différentes parties, ici le prog utilise 8 dlls. Maintenant pour que le dump soit opérationnel, il faut encore changé l'import adress RVA (avec peeditor) en 2a800, et également mettre l'eip original, celui trouvé au moment où le prog passe la main à celui décompressé (ici 421bfc).
Bon mais là amv ne marchera pas, pourquoi ? et bien si vous vous souvenez dans une partie précédent je vous ai dit qu'il mettait une adresse d'asprotect dans la table d'import qui servira pour une décryption si on a la clef. Et bien en fait il fait appel à GetProcAddress sauf pour un passage où il met cette valeur. Donc notre fixer a mis comme valeur pour ca une référence à GetProcAddress, et si le dump ne marche pas c'est que pour le passage pour la décryption et bien il fait appel à getprocaddress alors qu'il devrait mettre une valeur. Regardons le message d'erreur qui apparait au lancement du dump :
Crypt API not found. Please re-install
Donc on désassemble et on recherche ca et on arrive là :
00417620 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E
¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
00417620
00417620
00417620 sub_417620 proc
near
; CODE XREF: _WinMain@16+EBp
00417620
push 4
00417622
push 0FFFFFFFFh
00417624
call ds:off_4290C8
0041762A
test eax, eax
0041762C
mov ds:dword_433758, eax
00417631
jnz short
loc_417649 <-- on
force
00417633
push 10h
00417635
push offset aAdvancedMailLi ; "Advanced Mail List
Verify" <-- pas bon
0041763A
push offset aCryptApiNotFou ; "Crypt API not found.
Please re-install "...
0041763F
push eax
00417640
call ds:dword_4292E8
00417646
xor eax, eax
00417648
retn
00417649 ;
---------------------------------------------------------------------------
00417649
00417649
loc_417649:
; CODE XREF: sub_417620+11j
00417649
mov eax, 1
0041764E
retn
Pour eviter ce message d'erreur et la fin du prog il suffit de forcer le saut
en 417631 et le tours est joué. Maintenant le dump est complètement
fonctionnel et comme neuf :) . Biensur il y a encore une partie crypté
nécessitant la clef ... mais bon on a comme obtenu un dump d'amv avec une
véritable table d'import.
Bon si je vous est décrit le principe ce la méthode à Tsehp c'est parce que
c'est grace à cela que j'ai pu élaborer celle là, et oui il fallait savoir
où était le nom des fonctions ....
V_4ème méthode: pleditor |
Ce qui serait intérressant c'est que la méthode précédente marche
également sur pleditor (étant donné qu'il est protégé avec une version plus
récente d'asprotect). Et bien malheuresement elle ne marche pas car l'iat et
l'it n'éxiste plus, il n'y a que des 00. Cependant est il impossible pour
autant d'arriver au même objectif que précédement? et bien non mais il va
falloir arriver à mettre une méthode au point qui va créer une iat, une it et
pourquoi pas en même temps l'import descriptor. Mais comment peut on y arriver?
et bien c'est simple on ne va plus maintenant écrire un call fixer mais un
import table rebuilder. Comment est-ce possible ? et bien tout simplement parce
que asprotect se trahi lui même... Il est obligé de mettre c'est liens vers
les fonctions (qu'il y ai du cryptage ou juste un jmp ceci n'a pas d'importance)
au même endroit qu'il devait se trouvé originellement. En effet suposons qu'en
429000 on est un lien vers l'api CreateFileA, et bien le programme appelera ce
qu'il y a en 429000 : call [429000] et comme ceci fait partie de l'import table,
windows se chargera de remplacer ca par l'adresse de CreateFileA. Donc si
Asprotect veut reconstruire une import table, même une crypté ou n'importe
quoi d'autre, il est obligé que ce qui liera le prog à CreateFileA se trouve
en 429000. Et bien c'est comme cela que nous pourrons arriver à reconstruire
une iat, it, et l'import descriptor.
Je tiens à précisez que je n'aurais jamais pu mettre cette méthode au point
si Tsehp n'avait pas expliqué comment récupérer le noms des fonctions.
Bon maintenant mettons les choses en pratique. On lance pleditor, on trace, on enlève l'anti-debug et on arrive à cette fameuse boucle (voir toutes les parties précédentes pour y arriver) :
015F:00555392
AC
LODSB
015F:00555393
08C0
OR AL,AL
015F:00555395
74E4
JZ 0055537B
015F:00555397
4E
DEC ESI
015F:00555398
56
PUSH ESI
015F:00555399
53
PUSH EBX
015F:0055539A
80F802
CMP AL,02
015F:0055539D
7407
JZ 005553A6
015F:0055539F
0FB64E01
MOVZX ECX,BYTE PTR [ESI+01]
015F:005553A3
41
INC ECX
015F:005553A4
EB05
JMP 005553AB
015F:005553A6
B904000000
MOV ECX,00000004
015F:005553AB
41
INC ECX
015F:005553AC
01CE
ADD ESI,ECX
015F:005553AE
E8B5FDFFFF
CALL
00555168
<-- on redévi le call en jmp 556700
015F:005553B3
AB
STOSD
015F:005553B4
EBDC
JMP 00555392
015F:005553B6
61
POPAD
015F:005553B7
837D0800
CMP DWORD PTR [EBP+08],00
015F:005553BB
7509
JNZ 005553C6
Bon comme précédement on redévi le call vers un endroit libre, pour moi en 556700. Maintenant je vais essayer de vous expliquer le principe de de cette import rebuilder. Tout d'abord supposons que asprotect désire mettre son lien en 4b8000 et bien on prendra une place pour mettre notre it (les noms) en 4b8950 (par exemple) et on écrit le nom en 4b8950+2 (pour gérer l'ordinal mais qui ici n'y est pas) et on met en 4b8000 un lien vers 4b8950-image base (ici b8950 car l'image base est de 400000). Ensuite pour le cas où la fonction est appelé par ordinal on mettra directement l'ordinal en 4b8000 (enfin l'adresse à vers laquelle aspr pointe), on le met directement dans l'iat, mais pour que windows le reconnaisse il faudra ajouter 80000000 pour qu'il sache que c'est un ordinal et non l'adresse du nom de la fonction-2. Pour ce qui est de l'import descriptor on le reconstruira comme ceci ebx, contient une référence vers le nom de la dll en cours, il suffira de comparé ebx à chaque passage à ca valeur précendente pour savoir si on est toujours dans la même partie de l'import descriptor. Si non alors on reconstruit une structure comme ceci:
IMAGE_IMPORT_DESCRIPTOR struct
OriginalFirstThunk dd 0
;début de la zone où l'on va écrire les noms pour cette structure
TimeDateStamp dd 0 ;on met
00000000
ForwarderChain dd 0 ;on met FFFFFFFF
Name
dd 0 ;on écrit le nom de la dll avec le nom des fonctions et on met un
lien vers ce nom
FirstThunk dd
0 ;on le met égal à OriginalFirstThunk
IMAGE_IMPORT_DESCRIPTOR ends
Je sais tout ceci n'est expliqué que très grossièrement mais je ne sais pas trop comment le dire ... Bon maintenant on peut mettre en 556700 l'import rebuilder (il n'est en rien optimisé mais il fonctionne bien :) ) :
015F:00556700
E863EAFFFF
CALL
00555168
<-- on restaure le call
015F:00556705
60
PUSHAD
<-- on sauve les registres
015F:00556706 391DFC665500
CMP
[005566FC],EBX
<-- est-on tjs dans la même structure de
l'import descriptor
015F:0055670C
7470
JZ
0055677E
<-- oui on passe à la suite
015F:0055670E
60
PUSHAD
<-- non en en créér une nouvelle
015F:0055670F 891DFC665500
MOV
[005566FC],EBX
<-- on met ebx (pointeur vers le nom de la
dll) dans une variable
015F:00556715 8B15F4665500
MOV
EDX,[005566F4]
<-- on met l'adresse courante de l'iad
dans edx
015F:0055671B
893A
MOV
[EDX],EDI
<-- et on y met le contenu de edi (c'est à
dire le début de l'iat correspondant
015F:0055671D 812A00004000
SUB DWORD PTR
[EDX],00400000 <-- on enlève l'image base
015F:00556723 8305F466550008
ADD DWORD PTR
[005566F4],08 <-- on actualise le pointeur vers
l'iad
015F:0055672A 8B15F4665500
MOV
EDX,[005566F4]
<-- et on le met dans edx
015F:00556730 C702FFFFFFFF
MOV DWORD PTR
[EDX],FFFFFFFF <-- pour mettre ff.. dans la structure
015F:00556736
43
INC
EBX
<-- on fait pointer ebx sur la dll
015F:00556737
53
PUSH
EBX
<-- on met sur la pile
015F:00556738 FF35F8665500
PUSH DWORD PTR
[005566F8] <-- ainsi que sa
destination (adresse
courante de l'it)
015F:0055673E
E8B90BA2BF
CALL
KERNEL32!lstrcpy
<-- et on copi
015F:00556743 FF35F8665500
PUSH DWORD PTR
[005566F8] <-- on calcule la
longueur du nom
015F:00556749
E85F0CA2BF
CALL
KERNEL32!lstrlen
<-- dela dll
015F:0055674E
40
INC
EAX
<-- on incrémente pour le 0 de fin
015F:0055674F 8B15F8665500
MOV
EDX,[005566F8]
<-- on met l'adresse courante de l'it (contient
le nom de la dll)
015F:00556755 81EA00004000
SUB
EDX,00400000
<-- on enlève l'image base
015F:0055675B 8305F466550004
ADD DWORD PTR
[005566F4],04 <-- on actualise l'iad
015F:00556762 8B0DF4665500
MOV
ECX,[005566F4]
<-- et on met son adresse dans ecx
015F:00556768
8911
MOV
[ECX],EDX
<-- pour y mettre le pointeur vers le nom de la
dll
015F:0055676A
8B59F4
MOV
EBX,[ECX-0C]
<-- on récupère l'OriginalFirstThunk
015F:0055676D
895904
MOV
[ECX+04],EBX
<-- que l'on met dans le FirstThunk
015F:00556770 8305F466550008
ADD DWORD PTR
[005566F4],08 <-- on actualise l'iad
015F:00556777 0105F8665500
ADD
[005566F8],EAX
<-- ainsi que l'it
015F:0055677D
61
POPAD
<-- on a finit pour l'iad on passe à la
suite
015F:0055677E 833D6BFC740000
CMP DWORD PTR
[0074FC6B],00 <-- on a un nom de focntion
015F:00556785
743C
JZ
005567C3
<-- non alors on va vers crypté/ordinal
015F:00556787 8B15F8665500
MOV
EDX,[005566F8]
<-- oui on récupère l'adresse courante de
l'it
015F:0055678D 81EA00004000
SUB
EDX,00400000
<-- on enlève l'image base
015F:00556793
8917
MOV
[EDI],EDX
<-- et on met un lien dans l'iat
015F:00556795
686BFC7400
PUSH
0074FC6B
<-- on met l'adresse du nom de fonction
015F:0055679A 8B15F8665500
MOV
EDX,[005566F8]
<-- on met l'adresse courante de l'it
015F:005567A0
83C202
ADD
EDX,02
<-- on ajoute deux pour l'ordinal
015F:005567A3
52
PUSH
EDX
<-- on la met sur la pile
015F:005567A4
E8530BA2BF
CALL
KERNEL32!lstrcpy
<-- et on copie la procname
015F:005567A9
52
PUSH
EDX
<-- on met le nom de la fonction
015F:005567AA
E8FE0BA2BF
CALL
KERNEL32!lstrlen
<-- on calcule sa taille
015F:005567AF
0503000000
ADD
EAX,00000003
<-- on ajoute trois pour le zero de fin et
l'ordinal
015F:005567B4 0105F8665500
ADD
[005566F8],EAX
<-- on actualise l'it
015F:005567BA
61
POPAD
<-- on récupère les registres
015F:005567BB
83C704
ADD
EDI,04
<-- on passe au suivant
015F:005567BE
E9CFEBFFFF
JMP
00555392
<-- et on boucle
015F:005567C3
3D00000040
CMP
EAX,40000000
<-- eax a l'adresse d'une api
015F:005567C8
7D22
JGE
005567EC
<-- oui alors ordinal
015F:005567CA
83F900
CMP
ECX,00
<-- ecx = 0
015F:005567CD
751D
JNZ
005567EC
<-- oui alors ordinal
015F:005567CF
FF7001
PUSH DWORD PTR
[EAX+01] <-- on
passe le paramaètre pour la fonction
015F:005567D2
E8B5E8FFFF
CALL
0055508C
<-- de décryption
015F:005567D7 8B15F8665500
MOV
EDX,[005566F8]
<-- on met l'adresse courante de l'it dans
edx
015F:005567DD 81EA00004000
SUB
EDX,00400000
<-- on enlève l'image base
015F:005567E3
8917
MOV
[EDI],EDX
<-- et on met le lien dans l'iat
015F:005567E5
6864FC7400
PUSH
0074FC64
<-- on met l'adresse du nom de la fonction
015F:005567EA
EBAE
JMP
0055679A
<-- et on retourne la copié dans l'it
015F:005567EC
83EE04
SUB
ESI,04
<-- ordinal alors on le récupère
015f:005567EF
8B16
MOV
EDX,[ESI]
<-- dans edx
015f:005567F1 81C200000080
ADD EDX,
80000000
<-- on prépare l'ordinal pour windows
015F:005667F7
8917
MOV
[EDI],EDX
<-- on le met dans l'iat
015F:005667F9
61
POPAD
<-- on récupère les registres
015F:005567F2
83C704
ADD
EDI,04
<-- on passe au suivant
015F:005567F5
E998EBFFFF
JMP
00555392
<-- on boucle
015F:005567FA
0000
ADD [EAX],AL
015F:005567FC
0000
ADD [EAX],AL
Voilà , je pense avoir commenté au maximum, mais de toute facon c'est assez
dur à expliquer. Avant d'éxécuter la boucle il faudra mettre en 5566f4 le
début de l'endroit où vous voulez mettre l'iad ( j'ai mis 4b8950 car quand il
installe la fausse table d'import il met des liens jusque là, donc l'iat
s'arrêtera là). Ensuite en 5566f8 on met l'endroit où vous voulez mettre l'it
(j'ai mis 5566fc en pensant que 200h octets pour l'iad était sufisant. Il faut
également mettre les trois nops dans le call de décryptages (voir avant), mais
pour le repérer il faut étendre la recherche : s 0 l ffffffff 55 8b ec 81 c4
f8 fe ff ff 53 56 8b 5d 08 8b 03 89 45 f8 8d.
Maintenant on laisse tranquilement la boucle s'éxécuter et le rebuilder nous
reconstruire l'import table. Donc on aura pris soin de mettre un point d'arrêt
à la sortie de la boucle au niveau du popad.
Bon maintenant on dump tout ca : pagein d 4b8000 3000 c:\import.dat. On a
pris soin de faire le dump auparavant du programme et on colle notre nouvelle
table d'import dans le dump (voir précédement). Attention ici je n'avais pas
une VA égal au raw offset donc faites attention pour vous repérer dans le
programme. Bon une fois que cela est fait il reste encore a changé l'eip
(bf27c) et l'import table rva (b8950) qui correspond au début de l'import
descriptor.
Maintenant on lance le programme et ..... un beau message d'erreur :
Asprotect API not found! Running in unregistered mode. Hehe, le même problème
que sur amv, une partie du programme est crypté par clef. Bon pour enlever
cette écran un petit bpx messageboxa et on force le saut qui la précède .
Enfin voilà, maintenant il ne reste plus qu'à cracker le prog. Maintenant on a
un dump avec une vrai table d'import. Bon j'avoue que cette import rebuilder
n'est pas spécialement optimisé mais bon ... De plus j'ai essayé de vous en
expliquez le principe mais ce n'est pas spécialement facile ...
VI_Conclusion |
Voilà, cet essai sur le manual unpacking d'asprotect 1.0+ est fini. Je vous
ai présenté différente manière d'y parvenir, mais à mon avis l'import
rebuilder final est le plus intérressant car il reconstruit véritablement une
table d'import.
Bon voici quelques lectures supplèmentaires si vous en avez le courage :) :
http://assembly.citeweb.net/zip/IAT.htm
Texte très interressant sur le rajout d'une dll dans l'iat
http://christal.citeweb.net/tutor/cdilla.htm
Etude de cdilla por mieux comprendre le call fixer
http://tamambolo.free.fr
Etude de cdilla por mieux comprendre le call fixer
http://www.66.98.132.48/tsehp_asprotect105.htm
Le texte de Tsehp sur le dump d'asprotect (dernière version)
http://tsehp.cjb.net/
Pour les textes sur asprotect et cdilla
http://assembly.citeweb.net
Pour les textes sur le patching d'asprotect ...
Je tenais à remercier plusieurs personnes : Christal pour m'avoir relancé
dans cette protection, TeeJi pour avoir accepté d'étudier cette protection et
pour les discussions dessus sur irc, et +Tsehp pour avoir pris le temps de me
répondre et surtout pour avoir trouvé et mis au point (avec un très bon tut)
une première méthode de dump qui m'a finalement permis d'aboutir à l'import
table rebuilder.
Un special greetz à Alexey Solodovnikov qui nous prépare surement de bien
belle surprise pour l'avenir ...
Amicalement,
LuTiN NoIR
(c) 2000 . LuTiN NoIR - city_of_bitch@caramail.com