asprotect 2.xx et aip2 -...

45
ASPROTECT 2.XX ET AIP2 Dans ce tutorial, nous allons étudier une cible packée avec asprotect 2.1 SKE et qui est protégée avec AIP2 (advanced import protection version 2). cette protection se résume à des call asprotect qui émulent quelques apis et une IAT qui est partiellement endommagée. Notre but est d'arriver à obtenir un dump fonctionnel. Pour cela, nous devons remplacer les call asprotect par des références aux apis et s'arranger avec notre IAT partiellement détruite. Nous allons étudier successivement trois techniques différentes qui utiliseront des scripts pour ollydbg. I) METHODE METRO : remplacer les call asprotect par des jmp api direct et recréer une IAT avec UIF (Universal import fixer) II) METHODE ULYSSE_31 : décrypter les imports qui manquent dans l'IAT et remplacer les call asprotect par des jmp [api] indirects III) METHODE PNLUCK : nettoyer l'IAT en remplaçant les api cryptées par des dword null, rajouter les apis des call asprotects à la fin de l'IAT et remplacer les call asprotect par des jmp [api] indirects. J'ai nommé les différentes méthodes en hommage aux auteurs de tuts, scripts que j'ai lu sur asprotect :) La cible étudiée sera sticky password 3.4 (le setup est disponible dans l'archive) Pour écrire ce tutorial, je me suis beaucoup aidé du travail d'Ulysse31. Je le remercie donc vivement, et je conseille la lecture de son tutorial à tous ceux qui veulent une vision plus détaillée et complète d'asprotect http://www.savefile.com/download/1939748?PHPSESSID=ca166d56178707b2512dda32199a9872 mot de passe disponible sur le forum ici: http://deezdynasty.totalh.com/forum/viewtopic.php?f=1&t=688&start=0&st=0&sk=t&sd=a Vous allez peut être me dire, pourquoi s'embêter avec des scripts alors qu'on peut utiliser stripper? (un unpacker générique pour asprotect) Oui, mais ici stripper n'y arrive pas --> il faut donc essayer une autre solution... mars le 01/04/2009

Upload: others

Post on 26-Sep-2019

23 views

Category:

Documents


7 download

TRANSCRIPT

ASPROTECT 2.XX ET AIP2

Dans ce tutorial, nous allons étudier une cible packée avec asprotect 2.1 SKE et qui est protégée avec AIP2 (advanced import protection version 2). cette protection se résume à des call asprotect qui émulent quelques apis et une IAT qui est partiellement endommagée. Notre but est d'arriver à obtenir un dump fonctionnel.Pour cela, nous devons remplacer les call asprotect par des références aux apis et s'arranger avec notre IAT partiellement détruite. Nous allons étudier successivement trois techniques différentes qui utiliseront des scripts pour ollydbg.

I) METHODE METRO : remplacer les call asprotect par des jmp api direct et recréer une IAT avec UIF (Universal import fixer)

II) METHODE ULYSSE_31 : décrypter les imports qui manquent dans l'IAT et remplacer les call asprotect par des jmp [api] indirects

III) METHODE PNLUCK : nettoyer l'IAT en remplaçant les api cryptées par des dword null, rajouter les apis des call asprotects à la fin de l'IAT et remplacer les call asprotect par des jmp [api] indirects.

J'ai nommé les différentes méthodes en hommage aux auteurs de tuts, scripts que j'ai lu sur asprotect :)

La cible étudiée sera sticky password 3.4 (le setup est disponible dans l'archive)Pour écrire ce tutorial, je me suis beaucoup aidé du travail d'Ulysse31. Je le remercie donc vivement, et je conseille la lecture de son tutorial à tous ceux qui veulent une vision plus détaillée et complète d'asprotecthttp://www.savefile.com/download/1939748?PHPSESSID=ca166d56178707b2512dda32199a9872mot de passe disponible sur le forum ici:http://deezdynasty.totalh.com/forum/viewtopic.php?f=1&t=688&start=0&st=0&sk=t&sd=a

Vous allez peut être me dire, pourquoi s'embêter avec des scripts alors qu'on peut utiliser stripper?(un unpacker générique pour asprotect) Oui, mais ici stripper n'y arrive pas --> il faut donc essayer uneautre solution...

mars le 01/04/2009

INTRODUCTION

CONNAITRE LA VERSION D'ASPROTECT UTILISEEComme on est un peut curieux, on veut savoir avec quelle version d'asprotect, le prog est compresséUn petit coup de pied dernière version, et voilà le résultat:

Pour pied, c'est de l'asprotect 1.2 – 1.3 (la, il se plante un peu...)On utilise Protection ID pour avoir un deuxième avis:http://pid.gamecopyworld.com/ProtectionID_v6.1.6_2k9.raron cliques sur le boutton « scan », single file, puis sur la petite icône « protection report »

Pour protection ID, il s'agit bien de Asprotect 2.1 SKE

TROUVER L'OEP DU PROGRAMMEOn lance le prog et on a un petit message sympathique:

Pour passer cet anti debug, un plugin comme Hide Debugger avec IsDebuggerPresent activé suffit:http://www.openrce.org/downloads/download_file/238

La grande surprise avec ce prog protégé par asprotect, c'est qu'il n'y pas les classiques exceptions qui nous permettent d'approcher de l'OEP -->ici le programme se lance sans aucune exception...La technique que j'ai utilisée pour trouver l'OEP est donc la technique de la pile (cf début du tut d'Ulysse31)On execute le programme avec F9 et on remonte la pile à la recherche d'une référence à la section codedu prog. Un clic sur le menu View, memory nous donne les indications sur la section code de notre prog:

La section code s'étend de 401000 à 633000 --> on remonte donc la pile a la recherche d'une valeur comprise dans cette plage

En remontant la pile, on regarde toutes les références en rouge:– RETURN to kernel32.7C816D4F --> référence à une api, c'est pas bon– RETURN to stpass.00780477 from stpass.00780477 --> l'adresse 00780477 ne fait pas partie de notre section

code donc c'est pas bon– RETURN to 00F3FEE5 from 00F3FDCC --> ne fait pas partie de notre section code, pas bon– RETURN to 00F3FED7 from 00F3F050 --> meme chose, pas bon– RETURN to stpass.006327DB from stpass.00480EF8 --> ici les adresses sont dans notre section code --> on va

donc aller voir le code désassemblé --> clic droit sur la ligne, Follow in dissassembler

On se retrouve à l'adresse 6327DB juste après le call 480EF8 --> on remonte un peu et on voit le début caractéristique d'une procédure ou d'une OEP:

00632724 PUSH EBP ; --> l'OEP est ici00632725 MOV EBP,ESP

on se positionne en 632724 --> la routine en question n'est appelée par aucune autre routine --> c'est l'OEP

On constate dans le même temps, qu'il n'y pas de stolen bytes (quand les premières instructions à l'OEP sont effacées) --> c'est un peu plus dur de trouver l'OEP sinon.

Ci joint le listing désassemblé d'ollydbg avec l'OEP --> on remarque plusieurs lignes DD stpass.xxxxxxxx suivi de quelques instructions mal désassemblées avant l'OEP à l'adresse 00632724 --> en fait, c'est une table propre aux programmes écrits en Delphi --> elle contient des adresses pointant vers des procédures (on peut d'ailleurs s'aider de cette table pour trouver l'OEP lorsqu'il y a des stolen bytes)

fenêtre de dump en affichage long, adress ci dessous:

On peut aussi s'aider d'une autre particularité du delphi pour localiser l'OEP --> l'Entrypoint des progs en delphi se trouve toujours à la fin de la section code --> on exécute notre prog, on force l'analyse du code avec ollydbg et on va tout à la fin de la section code en 632FFF --> on cliques sur cette ligne et on remonte page par page jusqu'à trouver cette très longue table d'adresses (entourée en bleu) --> on reviens au début de la table et on est à l'OEP.

On peut vérifier tout ce qu'on vient de dire avec PEID:

On retrouve l'OEP avec le plugin « generic oep finder » de pied:

si on dumpe le fichier, et qu'on l'analyse avec pied en « hardcore scan », on voit que le prog est en delphi:

I) METHODE METR0j'appelle ça la méthode metr0 car c'est un tut de metr0 qui m'a donné l'idée de remplacer les call asprotects par des jmp api direct puis d'utiliser UIF pour créer une nouvelle IATPar contre, toute la partie analyse des call asprotect et une partie du script proviens du tutorial d'ulysse31.

LES CALL ASPROTECTSMaintenant qu'on a trouvé l'OEP, si on dumpe, et qu'on reconstruit les imports avec imprec, ça marche pas!!!Pourquoi? --> en fait, il va nous manquer quelques api --> dans ollydbg clic droit et search for, all intermodular call et on va au début de la liste

à coté des call qui font référence aux api (comme à l'adresse 4014D3 le call 00401480 qui fait référence a Kernel32.LocalAlloc) on a des call 01F80000 qui appellent une procédure de Asprotect qui les reexpedie vers des apis (par convention, je vais appeler ce call le call asprotect)Si on garde les call 01F80000 dans notre dump, ils ne feront plus référence aux api car les routines d'Asprotect ne seront plus actives.

Chez moi, le call asprotect est le call 01F80000, mais l'adresse peut être différente chez vous (cela dépend des zones mémoires disponibles pendant l'execution du prog)Pour repérer le call asprotect des autres, il suffit de trouver un call appelé plusieurs fois, qui n'appartient pas à la zone mémoire de notre prog, et qui se termine par 0000

La VM de asprotect attribue une api à chaque call 01F80000 en fonction de l'endroit ou il se trouve dans le code. Ainsi la solution pour avoir un dump valide, c'est de tracer chaque call 01F80000 pour voir à quel api il correspond et de modifier les call 01F80000 en call api avant de dumper.

TRACER UN CALL ASPROTECT POUR RETROUVER SON APIOn break à l'OEP du prog --> pour ça clic droit goto expression 632724, clic droit breakpoint harware on execution --> F9 et on est à l'OEPOn va rentrer dans le premier call asprotect de la liste, à l'adresse 4012F4 pour voir à quel api, il correspond.Dans ollydbg, on se positionne sur l'adresse 4012F4, clic droit, new origin here

On rentre dans le code avec F7 et on se retrouve dans une routine asprotect assez longue (j'ai mis juste le début, en plus c'est peut être pas exactement le même code chez vous)

On trace avec F8 (les différents sauts nous baladent un peu n'importe ou) jusqu'à un call ESI (ça peut être un autre registre chez vous) De toute façon le call se situe presque à la fin de la procédure (on peut apercevoir le RETN) On rentre dans le call ESI avec F7 (si on passe par dessus avec F8, on a une exception car on se fait repérer par un antidebug)

On se retrouve dans une autre partie assez longue de la VM d'asprotect:

On trace avec F8 jusqu'à reconnaître cette suite d'instructions:MOV EAX,EBXCALL XXXXXXXXJMP XXXXXXXXCALL YYYYYYYY

(un call, un jmp, un call)

on arrive ici:

on rentre dans le CALL XXXXXXXX avec F7 (si on fait pas gaffe et qu'on trace avec F8, notre trace over est détecté et on se récupère une exception). On se retrouve au début d'une autre routine asprotect:

On trace avec F8, mais cette fois ci, il faut regarder les registres car le nom de l'api doit apparaître dans eax ( mais si on a tracé un peu trop, on peux le voir plus loin dans edx aussi)

A la ligne 00F3E49C, on peut voir que EAX= Kernel32.GetStdHandle --> c'est notre apiLe call asprotect de la ligne 4012F4 correspond donc à l'api GetStdHandle.Pour remplacer notre call asprotect par le bon api, on se positionne a l'adresse 4012F4, clic droit « assemble » et on saisi JMP 7C812CA9 (api stocké en eax)

La, il faut sans doute que j'explique un peu --> peut être vous vous attendiez a un CALL 7C812CA9 plutôt qu'à un JMP 7C812CA9. En fait, les références aux apis se font de deux façons: soit avec des jmp, soit avec des call --> pour savoir s'il faut utiliser des jmp ou des call, il faut regarder les autres références aux api --> clic droit search for, all intermodular call:

on double clique sur une référence à une api (par exemple GetTickcount) et on voit ça:

on appuie sur entrée pour rentrer dans le call 00407830

On voit une foret de jmp api --> les références aux apis se font donc bien avec des jmp pour ce prog

Ici, les call ASProtect correspondent à des jmp [API], parce que c'est du Delphi et que le compilateur compile en call @1 / @1: jmp [API], contrairement à ce qui peut se faire avec du C où l'on a des call [API] directs.

voilà, on a donc tracé le call asprotect et fixé notre premier api --> maintenant, il faut automatiser la démarche avec un script (sinon, ça va être un peu long)

FAIRE UN CALL FIXER POUR ODBGSCRIPT

On va d'abord télécharger odbgscript qui est plugin pour ollydbg permettant d'executer des scripts.Il est mieux que ollyscript : plus rapide, plus d'instructions, une fenêtre qui nous permet d'exécuter pas à pas et de debugger notre script. Récupérez le fichier Odbgscript.dll qui se trouve dans l'archive, et copiez le dans le dossier d'ollydbg. Le plugin est ensuite accessible dans le menu plugin, ODbgScript

J'en profite aussi pour dire que la liste des commandes que l'on peut utiliser avec OdbgScript se trouvedans le fichier ReadMe.txt (joint dans l'archive)

ATTENTION: il est impératif d'utiliser Odbgscript --> ce script n'est absolument pas compatible avec ollyscript (certaines instructions ne sont pas reconnues). De plus, il est conseillé d'exécuter ce script avec une version non modifiée d'ollydbg

Voilà mon script pour fixer les call asprotect de sticky password:1_call fixer jmp direct pour creation IAT avec UIF.txt (dans l'archive)

Var OEP ; adresse de l'OEPVar API ; adresse ou on peut lire l'api dans eaxVar ASPR ; adresse du call asprotect à fixer

// met un breakpoint a l'oep et lance le progmov OEP, 00632724 ; OEP = 632724 dans sticky passwordbphws OEP, "x"; un breakpoint hardware on execution a l'oep

run ; on executeBPHWC ; on suprime le breakpoint hardware on execution de l'OEP

// met un breakpoint hardware la ou on recupere l'api dans eaxfindmem #8B45F48B80E00000000345E48945FC#mov API, $RESULT+Cbphws API, "x"; un breakpoint hardware on execution la on on voit l'api dans eax

// recupere les call asprotect dans une liste// trace chaque call asprotect jusqu'au breakpoint pour recuperer l'api// remplace les call asprotect par des jmp apiask "Enter redirected call ex: CALL 01FC0000"findcmd 401000, $RESULT ; search for call redirectedmov line,1 ; on commence par la ligne 1@boucle:gref line ; recupere l'adresse du premier callcmp $RESULT,0je @exitmov ASPR, $RESULT ; ASPR contient l'adresse du call asprotect que l'on veut fixer mov eip,ASPR ; simule un new origine here sur le call asprotectSTI ; on rentre a l'interieurrun ; on execute et quand on breake, on peut voir l'api dans eaxeval "jmp {eax}" ; on obtient notre jmp api (ex jmp 7CCC0000) dans $RESULTasm ASPR, $RESULT ; remplace notre call asprotect par notre jmp apiinc line ; augemente la ligne d'unjmp @boucle ; on passe au call asprotect suivant

// fin du script@exit:BPHWC ; on suprime tous les breakpoints hardware on execution.dec line ; on enleve 1 a line car la derniere ligne donne rieneval "Script finished!, {line} calls fixed" ; stocke la phrase dans $RESULTmsg $RESULT ; affiche $RESULT dans une msgbox

Pour savoir ou mettre le breakpoint qui nous permet de lire l'api dans, eax, j'ulise la fonction findmem:

FINDMEM what [, StartAddr]--------------------------Searches whole memory for the specified value.When found sets the reserved $RESULT variable. $RESULT == 0 if nothing found.The search string can also use the wildcard "??" (see below).Example:

findmem #6A00E8# // find a PUSH 0 followed by some kind of callfindmem #6A00E8#, 00400000 // search it after address 0040.0000

En fonction des exécutions, l'adresse ou on doit mettre notre breakpoint change. Ansi hier j'avais mis mon breakpoint en 00F3E49C

Aujourd'hui , la VM d'asprotect ne se charge pas au même endroit et je doit mettre mon breakpoint en 00DDE49C. Les adresses changent, mais le code ne change pas --> l'idee c'est donc de chercher le bloc d'instruction qui se trouve juste avant notre breakpoint.

On va donc lui faire chercher le code des quatre lignes grisées:00DDE490 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]00DDE493 8B80 E0000000 MOV EAX,DWORD PTR DS:[EAX+E0]00DDE499 0345 E4 ADD EAX,DWORD PTR SS:[EBP-1C]00DDE49C 8945 FC MOV DWORD PTR SS:[EBP-4],EAX ;

le code correspondant à ces instructions se trouve encadré en rouge sur le dessin. Il s'agit de8B45F48B80E00000000345E48945FCune fois que le script a trouvé notre chaine, il nous donne l'adresse de la première ligne dans $RESULT, mais nous ce qu'on veut, c'est la quatrième ligne --> c'est pour ça qu'on met un breakpointen $RESULT+C et pas en $RESULT

notre script trouve les call asprotect à fixer grace à l'instruction findcmd :

FINDCMD addr, cmdstr--------------------Search for asm command(s), you can search for series also with ";" separator.This command uses "Search for All Sequences" Ollydbg function so could find relative calls/jmpReference Window is used and its content changedYou can use GREF to get next results in disasm window range

Example 1:mov line,1findcmd eip, "xor R32,R32"

next:gref linecmp $RESULT,0je finishedinc linejmp next

finished:

l'instruction FINDCMD sort les call asprotect dans un tableau comme celui ci:

pour accéder aux adresses des différents calls, on utilise l'instruciton grefgref 1 nous renvoie l'adresse du premiere call 01E30000, soit 004012E4gref 2 nous renvoie l'adresse du deuxieme call 01E30000 soit 004012ECgref 3 nous renvoie l'adresse du deuxieme call 01E30000 soit 004012F4

--> On remarque au passage que le premier call à fixer est à l'adresse 004012E4 et pas à l'adresse 4012F4 comme on l'avait cru au début. --> quand on fait un clic droit, search for, all intermodular call, ça nous donne pas tous les call (il en oublie) --> c'est pour ça que je n'ai pas utilisé FINDCALLS dans mon script

Le reste du script ne devrait pas poser trop de problemes --> vous avez le fichier ReadMe.txt pour la signification des différentes commandes et vous pouvez debugger le script --> dans Odbgscript, script window, clic droit dans la fenêtre et sélectionner votre script. Vous pouvez faire du pas à pas avec la touche Tabulation et mettre des breakpoint avec F2

Dans l'exemple qui suit, rien qu'avec la troisième colonne « values », on peut voir ce qu'a fait le script --> il a mis un bp en 632724, mis un bp en DDE49C, cherché le CALL 01E30000, trouvé ce call pour la premiere fois en 4012E4, trouvé la référence à l'api 7C811069 et remplacé l'instruction à l'adresse 4012E4 par un JMP 7C811069.

EXECUTION DU SCRIPTon ouvre le fichier stpass.exe avec ollydbg, menu plugins, Odbgscript, run script et on selectionne « 1_call fixer jmp direct pour creation IAT avec UIF.txt » --> le script s'execute s'arrete à l'OEP --> il demande ensuite d'indiquer le call ASPROTECT (c'est à dire celui qui redirige les apis) --> on fait un clic droit dans ollydbg, search for, all intermodular call --> on va au debut de la liste et on regarde quel est le call asprotect:

Ici le call asprotect est CALL 01FC0000 mais il peut être différent chez vous, c'est pour ça qu'il faut vérifier avant d'enter le call.

Une fois qu'il a fini de fixer tous les calls, on obtient ce joli petit message:

ATTENTION: chez certains, le script ne traite pas tous les call asprotects et vous n'obtiendrez donc pas le message « 7F calls fixed » mais un nombre inférieur. --> si c'est le cas, essayer le script suivant dont le système de recherche est différent: 1bis_call fixer jmp direct pour creation IAT avec UIF.txt --> si vous n'obtenez pas vos 7F calls fixés, il est inutile d'aller plus loin car votre dump risque de ne pas fonctionner.

UTILISATION DE UIF (UNIVERSAL IMPORT FIXER)Tout ça c'est bien joli, mais on a fixé nos api avec des jmp directs. Or Imprec ne peut pas prendre nos jmp direct. On va donc se servir d'UIF qu'on peut télécharger ici:http://www.tuts4you.com/request.php?2286ci dessous une animation flash qui montre comment l'utiliser:http://www.tuts4you.com/request.php?2113

Ce prog va nous recréer une autre IAT et va transformer nos jmp directs en jmp indirects pour qu'ils fassent partie de la nouvelle IAT. On execute UIF.exe --> il nous demande le process ID de notre prog dans ollydbg:On clique sur le menu file, attach d'ollydbg et on va chercher stickypassword --> il est en rouge dans la liste puisqu'on le debugue --> clic droit, copy to clipboard process.

Ici le process ID de stickypassword est 00000D30 (valeur dans la premiere colonne)

Dans Universal Import Fixer, on a trois chose à faire:1) on colle le Process ID dans le champ2) on coche la case « Fix Directly imports »3) on clique sur start

quand il a fini, il nous affiche les informations sur la nouvelle IAT qu'il a crée,son adresse, et sa taille:

si on veut regarder ce qu'a fait UIF à notre prog, on regarde le premier api qu'on avait fixé en direct:clic droit, goto expression, 4012F4:

UIF a donc fait le boulot, il a transformé nos jmp directs en jmp indirects qui se réfèrent a une IATOn peut d'ailleurs aller voir un bout de notre nouvelle IAT en 0200002C dans la fenetre de dump d'ollydbg, et clic droit long, adress pour avoir l'affichage correct:

DUMP AVEC LORDPE ET RECONSTRUCTION DES IMPORTS AVEC IMPRECon peut maintenant dumper stickypassword avec lordPe par exemple car UIF a accompli sa missionsi vous avez plusieurs occurrences de stickypassord lancées,vérifier bien qui s'agit du bon Process ID(la même chose pour imprec, verifier bien le process ID --> ici 00000D30)

On lance imprec, on selectionne stpass.exe dans la liste des actives process, et on va devoir remplirles champs classiques d'imprec:

1) OEP --> trouvé au début du tut 00632724 --> on enleve 400000 d'image base et on a 2327242) 01C00000 --> donné par UIF a la fin3) A64 --> donné par UIF a la fin

On cliques directement sur « getImports »--> il nous trouve plein d'imports tous valides --> cool ça ;)

maintenant, il n'y a plus qu'a fixer tout ça sur notre dump --> avant ça on cliques sur le bouton « Options »d'imprec (entouré en bleu), et on coche obligatoirement les trois cases entourées en rouge (si on le fait pas, imprect ne pourra pas fixer les imports dans notre dump)

on cliques sur le bouton « Fix Dump » (entouré en vert) et un aprés 40 bonnes secondes (oui je sais, c'est long...), imprec nous dit qu'il a fini:

on execute le fichier dumped finale_exe pour voir si tout est ok......et ça marche!!!

--> un petit schéma pour récapituler tout ça:

SCHEMA METHODE METR0 (POUR AIP1 OU AIP2)

programme à l'Entry Point

étape 1: on execute le script 1_call fixer jmp direct pour creation IAT avec UIF.txt --> il s'arrete à l'OEP puis il trace dans chaque call asprotect pour voir à quel api il correspond --> ensuite il remplace chaque call asprotect par un jmp api

4012E4 CALL 01E30000 remplacé par4012E4 JMP 7C812CA9 ; kernel32.GetStdHandle

étape 2: on execute UIF (universal import fixer) --> il va remprendre nos jmp directs, les jmp indirect du prog et va créer une nouvelle IAT puis modifier le code du prog pour qu'il utilise la nouvelle IAT avec que des jmp indirects

4012E4 JMP 7C812CA9 ; kernel32.GetStdHandle remplacé par4012F4 JMP DWORD [0200002C] ; kernel32.GetStdHandle

IAT DE DEPART00640300 7C8099BD kernel32.LocalAlloc00640304 7C8092AC kernel32.GetTickCount00640308 4AE84D5F 0064030C 7C8114AB kernel32.GetVersion NOUVELLE IAT 0200002C 7C812CA9 kernel32.GetStdHandle02000030 7C81EAE1 kernel32.RaiseException02000034 7C80180E kernel32.ReadFile02000038 7C81F850 kernel32.SetEndOfFile

etape3: on dumpe avec lordPe (on a des jmp indirects, imprec va etre content)

DUMP

etape4: on lance imprec --> va reconstruire nos imports avec ce que lui donne UIF comme info sur la nouvelle IAT

programme unpacké en dur

II) METHODE ULYSSE_31

ADVANCED IMPORT PROTECTION (AIP1 et AIP2)

Avec asprotect, les imports peuvent être protégés avec deux niveaux de protection: AIP1 et AIP2

Dans les deux cas, il y a des call asprotect à fixerla seule différence entre AIP1 et AIP2 c'est l'état de l'IAT de départ:

1)voilà une IAT nickelle avec AIP 1 (icon constructor au début du tut d'Ulysse)

00480014 77DBA122 ADVAPI32.CryptHashData00480018 77DBA43C ADVAPI32.CryptGetHashParam0048001C 77DBA254 ADVAPI32.CryptDestroyHash00480020 77DB8546 ADVAPI32.CryptReleaseContext00480024 77DA7883 ADVAPI32.RegQueryValueExA00480028 00000000 --> le 00000000 est normal car il sépare deux dll différentes ADVAPI32 et comctl320048002C 58B81D6B comctl32.ImageList_GetIcon00480030 00000000 --> le 00000000 est normal car il sépare comctl32 et GDI3200480034 77EF9313 GDI32.SetDIBitsToDevice00480038 77EF82A1 GDI32.GetCurrentObject0048003C 77EF9FC5 GDI32.GetDIBits00480040 77EF9A82 GDI32.GetObjectA

2) voilà une IAT partiellement détruite avec AIP 2 (Icon to any a la fin du tut d'Ulysse)

004E61BC 7C809A81 kernel32.VirtualAlloc004E61C0 7C80995D kernel32.LocalFree004E61C4 7C8099BD kernel32.LocalAlloc004E61C8 7C809794 kernel32.InterlockedDecrement004E61CC 7C80977B kernel32.InterlockedIncrement004E61D0 7C80B859 kernel32.VirtualQuery004E61D4 7C80A0C7 kernel32.WideCharToMultiByte004E61D8 672B3BE9 --> il manque une api ici004E61DC 7C809CAD kernel32.MultiByteToWideChar004E61E0 BFC194F4 --> il manque une api ici004E61E4 7C810311 kernel32.lstrcpynA004E61E8 7C80C729 kernel32.lstrcpyA

voyons maintenant à quoi ressemblent les imports de sticky password --> on lance le programmeet clic droit, search for, all intermodular call --> on va sur un import de la liste en double cliquant dessus

004014D3 CALL stpass.00401480 kernel32.LocalAlloc

--> on appuie sur entrée pour rentrer dans le call

00401480 JMP DWORD PTR DS:[640300] ; kernel32.LocalAlloc

--> pour trouver l'IAT, on fait un clic droit sur cette ligne, Follow in dump, memory adresset on obtient l'IAT dans la fenêtre de dump (pour voir correctement, clic droit, long et adress):

00640300 7C8099BD kernel32.LocalAlloc00640304 7C8092AC kernel32.GetTickCount00640308 4AE84D5F --> il manque une api ici0064030C 7C8114AB kernel32.GetVersion00640310 9B6B1A52 --> il manque une api ici00640314 7C809794 kernel32.InterlockedDecrement00640318 7C80977B kernel32.InterlockedIncrement0064031C 7C80B859 kernel32.VirtualQuery00640320 7C80A0C7 kernel32.WideCharToMultiByte00640324 20666468 --> il manque une api ici00640328 7C809CAD kernel32.MultiByteToWideChar0064032C 91C02019 --> il manque une api ici

les imports de sticky password ressemblent à ceux de Icon to any --> y a des imports qui manquent puisqu'il y a des valeurs qui ne sont pas le séparateur 00000000 et qui correspondent à aucun api--> donc c'est protegé avec AIP 2 (Advanced Import Protection 2)

Avec METHODE METR0, on a pas reconstruit nous même l'IAT, c'est UIF (Universal import fixer) qui s'en ai chargé.

Au contraire, avec la METHODE ULYSSE_31, on reconstruit l'IAT si elle est endommagée, et on remplace aussi directement les call asprotect avec des jmps ou call api indirects --> il n'a donc pas besoin de UIF, mais juste de Imprec pour terminer le travail. Nous allons d'abord expliquer cette méthode pour le cas le plus simple, c'est a dire pour AIP1 (quand l'IAT n'est pas endommagée)

Avec METHODE ULYSSE_31 POUR AP1, quand on a trouvé l'api qui correspond à un jmp asprotect, on ne remplace pas le call asportect par un jmp api direct comme on le faisait avec METHODE METR0, on scrute l'IAT à la recherche de l'api en question --> quand on l'a trouvé, on peut remplacer le call asprotect par un un call [api] c'est à dire un appel indirect pointant sur l'IAT.

Ensuite, on verra METHODE ULYSSE_31 POUR AP2 qui est la même chose que METHODE ULYSSE_31 POUR AP1 --> il a juste une étape préliminaire qui est la reconstruction de l'IAT (partie en rouge dans le schéma)

SCHEMA METHODE ULYSSE_31 POUR AP1 (exemple avec icon constructor)

programme à l'OEP

étape 1: on exécute le script 3_call fixer jmp indirect pour IAT complète ou reconstruite.txt à l'OEP--> il trace dans chaque call asprotect (call 00F40000) pour voir à quel api il correspond--> par exemple, il voit que le call 00F40000 de la ligne 4012C4 correspond à un CALL 7CA1FE44 ensuite il recherche la valeur 7CA1FE44 dans l'IAT --> il trouve cette valeur à l'adresse 4805A0 --> il peut donc remplacer le call asprotect par un call [4805A0] (c'est à dire un saut indirect qui pointe vers l'api ShellExecuteA)

4012C4 CALL 00F40000 remplacé par4012C4 CALL DWORD [004805A0] ; shell32.ShellExecuteA

IAT004805A0 7CA1FE44 shell32.ShellExecuteA004805A4 7CAC6696 shell32.SHGetSpecialFolderPathA004805A8 7CA0AC27 shell32.SHChangeNotify004805AC 7CA23AB1 shell32.SHGetPathFromIDListA004805B0 7CA83FB3 shell32.DragQueryFileA

etape2: on dumpe avec lordPe (on a des jmp indirects, imprec va être content)

DUMP

etape3: on lance imprec --> va reconstruire nos imports en lui donnant le début et la fin de l'IAT qu'on a renseigné au script au début

programme unpacké en dur

SCHEMA METHODE ULYSSE_31 POUR AP2

programme à l'EntryPoint

étape préliminaire: on exécute le script 2_Aspr2_IAT_rebuilder.txt --> au départ, la table iat est cryptée --> notre script force le prog à décoder

toute notre IAT et pas seulement une partie --> ensuite il nous rend la main à l'OEP

avant le script : IAT TOTALEMENT CRYPTEE004805A0 12341234 --> api qu'il ne va pas résoudre normalement004805A4 22557788 --> api qui sera décodé normalement004805A8 45674567 --> api pas résolue normalement004805AC 99558833 --> sera décodé normalement004805B0 33559900 --> sera décodé normalement

:après le script: IAT TOTALEMENT RESTAUREE004805A0 7CA1FE44 shell32.ShellExecuteA --> on le force à décrypter cet api004805A4 7CAC6696 shell32.SHGetSpecialFolderPathA004805A8 7CA0AC27 shell32.SHChangeNotify --> et aussi cet api qui aurait manqué sinon004805AC 7CA23AB1 shell32.SHGetPathFromIDListA004805B0 7CA83FB3 shell32.DragQueryFileA

programme à l'OEP

étape 1: on exécute le script 3_call fixer jmp indirect pour IAT complète ou reconstruite.txt à

l'OEP (comme pour le schéma AIP1)

4012C4 CALL 00F40000 remplacé par4012C4 CALL DWORD [004805A0] ; shell32.ShellExecuteA

etape2: on dumpe avec lordPe (comme pour le schéma AIP1)

DUMP

etape3: on lance imprec --> va reconstruire nos imports en lui donnant le début et la fin de l'IAT qu'on a renseigné au script au début

programme unpacké en dur

RECONSTRUIRE UNE IAT ENDOMAGEE

D'abord, un petit rappel sur l'utilité de reconstruire une IAT abimée (partie rouge de la METHODE ULYSSE_31 POUR AP2). Si on exécute que le script conçu pour AIP1, dans un programme protégé avec AIP2, il trouve l'api , mais il va y avoir un problème quand il cherche dans l'IAT pour trouver un saut indirect car certains apis ont été effacés de l'IAT donc il ne trouvera pas le saut indirect correspondant --> c'est pour ça qu'il faut restaurer l'IAT avant de fixer les call asprotect

Maintenant, comment Ulysse s'y prend t'il pour reconstruire l'IAT --> pour comprendre, il faut regarder à quoi ressemble l'IAT à l'EntryPoint c'est à dire avant de lancer l'execution du prog:

à l'Entrypoint, l'IAT est entièrement cryptée: (ci joint le début de l'IAT de stickypassword)

006402E4 FEF956D1006402E8 74A1AD68006402EC 1763C73D006402F0 C850EC21006402F4 B7336A2D006402F8 F7807FE0006402FC 2C7CBCE000640300 B3C44C5400640304 D2813ABC00640308 93E56C7C0064030C 183F9B4400640310 A355A1C400640314 49757F84

à l'OEP, l'IAT est partiellement endommagée --> elle a été décryptée qu'en partie.

006402E4 7C92188A ntdll.RtlDeleteCriticalSection006402E8 7C9110ED ntdll.RtlLeaveCriticalSection006402EC 7C911005 ntdll.RtlEnterCriticalSection006402F0 7C809FA1 kernel32.InitializeCriticalSection006402F4 7C809B14 kernel32.VirtualFree006402F8 7C809A81 kernel32.VirtualAlloc006402FC 7C80995D kernel32.LocalFree00640300 7C8099BD kernel32.LocalAlloc00640304 7C8092AC kernel32.GetTickCount00640308 4AE84D5F --> la il manque un api0064030C 7C8114AB kernel32.GetVersion00640310 9B6B1A52 --> la il manque un api aussi00640314 7C809794 kernel32.InterlockedDecrement

L'idée, c'est de mettre un bp harware on write sur le début de l'iat, et d'exécuter comme ça quand aprotect va écrire pour remplacer l'api crypté par l'api décodée, on va atterrir en plein dans la routine d'asprotect qui decrypte l'IAT. On met un bp harware, on write, dword, sur la première adresse 006402E4 et on execute avec F9 --> ça break, on continue avec F9 tout en gardant un oeil sur l'IAT (dump à l'adresse 6402E4)Au sixieme break, on voit qu'il a decrypté totalement la premiere api de l'IAT:

on se trouve alors ici dans le code:

a partir de la, on execute avec F8, tout en regardant encore le dump pour voir quand le deuxieme api sera décrypté:

Le deuxième api (ntdll.RtlLeaveCriticalSection) est décrypté par le call encadré en rougeOn continue d'executer avec F8 --> en fait on se retrouve dans une grande boucle qui vadecrypter tous les apis de l'IAT --> la prochaine fois qu'on passe par le CALL 00F354F4, on rentre dedans avec F7 --> voilà ce qu'on voit quand on rentre dans le call, et on trace un peu avec F8 jusqu'à ce qu'on reconnaisse un passage du tut d'ulysse sur le décryptage de l'IAT de Icon to Any v3.02

les deux adresses encadrées en rouge et bleu vont nous suffire pour faire notre script qui décodera l'IAT. Comme expliqué dans le tut d'Ulysse, quand il est prévu que l'api soit décrypté, le saut conditionnel entouré en rouge ne saute pas. Si on veut forcer le décryptage d'une api qui ne devait pas être décryptée à l'origine, il faut inverser le saut conditionnel, mais aussi modifier la clef à l'endroit encadré en bleu. (la clef est en ecx, et il faut lui enlever 0A)

voilà un petit schéma pour résumer tout ça :

si api résolue, on ne saute pas

00F35530 JNZ SHORT 00F35590

si api non résolue,on saute

pour résoudre l'api, on inverse le saut mais on modifie aussi la clef de décryptage(on enlève 0A à ECX)

On va tester notre petit raisonnement avec la prochaine api non résolue qui est la suivante:00640308 4AE84D5F --> la il manque un apion met un breakpoint hardware on execution sur le saut conditionnel et on execute avec F9 jusqu'à ce que ça soit le tour de notre api non résolue

On voit avec le « jmp is taken » encadré en rouge qu'on s'apprête à sauter --> cette api ne seradonc pas résolue et la valeur restera à 4AE84D5F. Comme on veut résoudre cette api,on inverse le zero flag pour ne pas sauter. On trace jusqu'à l'adresse F3556A encadrée en bleu ou il faut modifier la clef.

Quand on arrive ici, on a ECX = 00FC15D2 On va modifier ECX pour que ECX = ECX – 0A --> on saisit directement ECX = FC15C8dans les registres et on exécute pas à pas avec F8 --> au bout de quelques instructions, on voit que notre api qui ne devait pas être résolue a finalement été résolue par nos soins

LE SCRIPT 2_ASPR2_IAT_REBUILDER.TXT

Maintenant qu'on a compris le principe, ça serait dommage de reconstruire toute l'IAT à la main --> autant utiliser un script pour automatiser tout ça. J'ai pris comme base le script minimal d'Ulysse, et j'ai rajouté quelques lignes pour qu'il s'arrête à l'OEP quand il a fini de décrypter toute l'IAT (dans sa version de base, il s'arrête pas...)

J'ai repris ce script d'Ulysse car je le trouve vraiment simple à comprendreCe script fonctionne chez moi, mais chez vous, vous aurez sans doute besoin de changer:– l'adresse du saut conditionnel ( 00F75530 chez moi)– l'adresse la ou on modifie la clef ( 00F7556A chez moi)

// dit au script de s'arreter à l'oepbphws 00632724, "x" ; breakpoint hardware on execution sur l'OEPbpgoto 00632724, @End ; va a @End si break sur l'OEP

// on modifie le saut conditionnel et la clef si besoinbphws 00F75530, "x" ; bp hardware exec sur le saut conditionnel@start:run

@loop:cmp !ZF, 1 ; si ZF=1 (saute pas) on laisse faire le progje @startmov !ZF, 1 ; sinon (si saute) on inverse le flagbphws 00F7556A, "x" ; on met un bp la ou on doit modifier la clefrunbphwc 00F7556A ; quand ça break, on enleve le breakpointsub ecx, 0A ; on modifie la clef ecx (on lui enleve 0A)runjmp @loop

// fin du script@End:bphwc ; on enleve tous les breakpoints

--> on exécute le script --> on obtient une IAT nickelle et notre prog s'arrête à l'OEP pour la suite des réjouissances.

REMPLACER LES CALL ASPROTECT PAR DES CALL [API]

Maintenant qu'on a restauré l'IAT avec le script 2_Aspr2_IAT_rebuilder.txt, on va tracer les call asprotect pour voir a quelle api ils correspondent. On va ensuite tracer l'IAT pour retrouver l'api et on va au final remplacer les call asprotect par des jmp [api](voir fin du schéma METHODE ULYSSE_31 POUR AP2)

On aurait pu utiliser la technique précédente pour retrouver l'api (on pose un breakpoint en 00F3E49C, on trace le call asprotect, et quand ça break, on peut voir l'api dans eax), mais la, on va utiliser une technique différente (faut bien apprendre...). Cette technique c'est l'api hooking ou interception d'api --> on met un breakpoint sur un api et on récupère immédiatement les informations désirées...

Asprotect, utilise une api intéressante: LoadLibraryA de Kernel32.dll. On peut faire de grandes choses si on arrive à breaker sur cette api. Le problème, c'est que si on met notre breakpoint directement sur LoadLibraryA, ça break pas quand on trace un call asprotect. Pourquoi? --> en fait asprotect asprotect émule juste les premières instructions de l'api (pour éviter que ça break quand on met notre bp au début de l'api).Conclusion, si on veut breaker sur l'api LoadLibraryA, il ne faut pas mettre notre bp au début mais quelques instructions plus tard.

Les premières instructions encadrées en bleu sont émulées par asprotect, il faut donc mettre notre breakpoint en dessous --> on va mettre notre breakpoint hardware on execution sur le saut conditionnel qui suit le début de l'api (ligne grisée à l'adresse 7C801D82 chez moi)

maintenant, on va tracer un call asprotect --> clic doit, goto, expression, 0053E89C

0053E89C CALL 01F80000 ; call asprotect

clic droit sur la ligne, new origin here, et F9ollydbg break, au niveau de l'api LoadLibraryA, et on peut voir ça dans les registres:

EAX 00E4A068 ASCII "advapi32.dll" --> le nom de la dllECX 00E215A8EDX 00E215A8EBX 00000014ESP 0012FD38EBP 0012FD40ESI 00E215A0EDI 0012FD80 ASCII "GetTokenInformation" --> le nom de l'api

on se rend compte que l'api qui va bien est "GetTokenInformation" de la dll "advapi32.dll"

Maintenant comment retrouver l'adresse de cette api dans ollydbg? On cliques sur le menu « view », « executables module » dans ollydbg et on selectionne la dll "advapi32.dll" dans la listeclic droit et view names --> les api sont classées par ordre alphabetique:

ici, l'adresse de notre api "GetTokenInformation" est 77DA7B76

Maintenant, c'est pas fini --> on ne remplace pas betement le call asprotect par un Jmp 77DA7B76, on va chercher la valeur 77DA7B76 dans notre IAT restaurée:dans la fenetre de dump, on se positionne d'abord au début de l'IAT --> goto expression 6402E4et on recherche ensuite notre api --> clic droit search for, adress, 77DA7B76

On tombe sur la ligne grisée --> l'adresse dans l'IAT qui correspond à cette api est 640434 (entourée en rouge) --> on peut donc remplacer le call asprotect de l'adresse 0053E89C par un jmp [640434]

0053E89C CALL [640434] ; advapi32.GetTokenInformation

Maintenant qu'on a compris le principe, on va pouvoir faire un script:(rappel: d'abord exécuter le script ASPR2_IAT_REBUILDER_MOD.TXT pour restaurer l'IAT avant d'exécuter ce script)

LE SCRIPT 3_CALL FIXER JMP INDIRECT POUR IAT COMPLÈTE OU RECONSTRUITE.TXT

// on fixe directement le call asprotect de la ligne 433EA4// avec l'adresse correcte de l'iatmov [433EA4],0025FF ; on remplace le call asprotect par un jmp [api]mov [433EA6],00640D50 ; on modifie la suite du jmp [api] avec l'adresse de l'IAT

Var OEP ; adresse de l'OEPVar API ; adresse ou on peut lire l'api dans eaxVar ASPR ; adresse du call asprotect à fixervar IATStart ; adresse du debut de l'IATvar IATEnd ; adresse de la fin de l'IATvar y ; adresse ou on doit mettre un bp pour récuperer l'api

// demande le début et la fin de l'IAT// si on rentre rien, va a @err1ask "Enter IAT start:"cmp $RESULT,0je @err1mov IATStart,$RESULTask "Enter IAT end:"cmp $RESULT,0je @err1mov IATEnd,$RESULT

// recupere les call asprotect dans une listeask "Enter redirected call ex: CALL 01FC0000"findcmd 401000, $RESULT ; search for call redirectedmov line,1 ; on commence par la ligne 1

// met un breakpoint sur le saut conditionnel qui suit l'api loadlibrarygpa "LoadLibraryA","kernel32.dll"mov LLA_Offset,$RESULTfind LLA_Offset, #74#mov y,$RESULT ; y est l'adresse du saut conditionnelbphws y, "x" ; on met un breakpoint hardware dessus

// trace chaque call asprotect jusqu'au breakpoint pour recuperer l'api@boucle:gref line ; recupere l'adresse du premier callcmp $RESULT,0je @exitmov ASPR, $RESULT ; ASPR contient l'adresse du call asprotect que l'on veut fixer mov eip,ASPR ; simule un new origine here sur le call asprotectSTI ; on rentre a l'interieurrun ; quand on breake, on peut voir le nom de l'api dans edx et la library dans eaxgpa [edi], [eax] ; l'api est dans $RESULT aprés cette instruction

// on cherche l'api dans l'IATmov api,$RESULTmov $RESULT,IATStart@Search_IAT:cmp [$RESULT],apije @APIFoundadd $RESULT,04cmp $RESULT,IATEndja @err2jmp @Search_IAT

// quand on a trouvé l'api dans l'iat, on remplace le call asprotect par un jmp [api]@APIFound:mov [ASPR],0025FF ; on remplace le call asprotect par un jmp mov [ASPR+2],$RESULT ; on modifie la suite du jmp [api] avec l'adresse de l'IATinc line ; augemente la ligne d'unjmp @boucle ; on passe au call asprotect suivant

@err1:msg "Error! Check these values!"jmp @exit

@err2:msg "API not found in IAT!"jmp @exit

// fin du script@exit:BPHWC ; on suprime tous les breakpoints hardware on execution.eval "Script finished!, {line} calls fixed" ; stocke la phrase dans $RESULTmsg $RESULT ; affiche $RESULT dans une msgbox

Ce script a pas mal de choses en commun avec le premier, donc je vais expliquer seulement les points qui différent (quatre rectangles de couleur)

la partie bleue est d'abord chargée de mettre un bp sur le saut conditionnel qui suit le début de l'api loadlibrary --> pour cela, on va utiliser l'instruction GPA d'odbgscript:

GPA proc, lib, [0,1]--------------------Gets the address of the specified procedure in the specified library.When found sets the reserved $RESULT variable. $RESULT == 0 if nothing found.Useful for setting breakpoints on APIs.Set third param to 1 if you want to keep library in memoryExample:

gpa "MessageBoxA", "user32.dll" // After this $RESULT is the address of MessageBoxA and you can do "bp $RESULT".

Avec l'instruction gpa "LoadLibraryA","kernel32.dll"il nous trouve l'adresse du début de "LoadLibraryA" dans $RESULT on sauve ce résultat dans LLA_Offset avec l'instruction mov LLA_Offset,$RESULT--> mais nous ce qu'on veut c'est l'adresse du saut condtionnel qui suit --> on fait donc une recherche sur un JE a a partir de cette adresse avec find LLA_Offset, #74#il nous trouve l'adresse dans $RESULT --> on sauve ça dans y avec mov y,$RESULT et on met un bp hardware on exécution sur le JE avec bphws y, "x"

La partie rouge est chargée de récupérer l'adresse de l'api d'après les informations visibles dans les registres aprés le breakpoint sur LoadLibraryA

dans notre exemple, on voyait ça dans les registres:

EAX 00E4A068 ASCII "advapi32.dll" --> le nom de la dllECX 00E215A8EDX 00E215A8EBX 00000014ESP 0012FD38EBP 0012FD40ESI 00E215A0EDI 0012FD80 ASCII "GetTokenInformation" --> le nom de l'api

l'instruction gpa [edi], [eax] réalise un gpa "GetTokenInformation", "advapi32.dll"Cela nous retourne l'adresse de "GetTokenInformation" 77DA7B76 dans $RESULT

La partie verte cherche cherche 77DA7B76 dans l'IATon copie cette adresse dans api avec mov api,$RESULTon copie IATStart l'adresse du début de notre IAT dans $RESULT avec mov $RESULT,IATStarton compare $RESULT à notre api avec cmp [$RESULT],api (au départ $RESULT = IATStart)si c'est pas égal, on passe à l'api suivante qui se trouve 4 bytes plus loin avec add $RESULT,04on continue et on passe aux api suivante de l'IAT jusqu'à ce que api = $RESULT. Si c'est le cas on saute en @APIFound avec je @APIFoundSi on a pas trouvé notre api et qu'on arrive à la fin de l'IAT cmp $RESULT,IATEndl'instruction suivante ja @err2 nous amene en @err2 ou on a le droit au message d'erreur suivant« api not found in IAT » (mais normalement, ça devrait pas arriver...)

quand on a trouvé notre api dans l'IAT, la partie jaune se charge de remplacer le call asprotect par un JMP [api]. J'aurais bien utilisé l'instruction ASM pour faire ça, mais apparement, elle ne supporte pas les saut indirects --> j'utilise donc la méthode d'ulysse mov [ASPR],0025FF ; pour mettre un jmp a la place du call asprotect (adresse ASPR)mov [ASPR+2],$RESULT ; pour mettre l'adresse de l'iat qui pointe sur l'api en suivant

remarque: au tout début du script, vous avez peut être remarqué les instructions suivantes:mov [433EA4],0025FFmov [433EA6],00640D50en fait je triche un peu --> je fixe d'avance le call asprotect de la ligne 433EA4 qui aurait fait buggé mon merveilleux script. Ce call correspond à l'api "InitCommonControls" de la dll "comctl32.dll"en temps normal cette dll (version 5.82) se charge en memoire a l'adresse 58B5000058B50000 comctl32 5.82 C:\WINDOWS\system32\comctl32.dllmais avec stickypassword, c'est une autre version de la dll qui est utilisée (version 6.0): 77390000 comctl32 6.0 C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.2180_x-ww_a84f1ff9\comctl32.dll

Or l'instruction gpa [edi], [eax] du script nous retourne 58B715DD (l'adresse de l'api "InitCommonControls" pour la version 5.82), alors que ça aurait du nous retourner 7739405C (l'adresse de l'api pour la version 6.0) --> le problème c'est que le script de restauration de l'IAT à bien mis 7739405C pour l'api "InitCommonControls", donc si la fonction gpa trouve 58B715DD, on aura le message d'erreur au niveau de la recherche de l'api dans l'IAT "API not found in IAT!"

Cette utilisation de différentes version d'une api ne se produit pas dans tous les softs donc il se peut très bien que mon script marche sans problème pour un autre logicielMoi, j'ai préféré patcher directement cette instruction plutôt que de réécrire complètement mon script --> mon script est avant tout à usage pédagogique --> si vous voulez un script qui fonctionne à tous les coups, vous pouvez prendre celui d'ulysse 3bis_ASProtect 2.xx_call-fixer2 spass.txt (il n'utilise pas l'instruciton gpa mais il se sert de l'api getprocadress en lui donnant les arguments adéquats)

--> on execute le script. Il nous demande:– l'IAT start --> la première api de l'IAT est à l'adresse 006402E4– l'IAT end --> la dernière api de l'IAT est à l'adresse 00640E58– Le call asprotect (chez moi CALL 01FC0000)

Au bout de quelques minutes, on obtient le message de réussite suivant:

Si vous n'obtenez pas le même nombre de call fixés (7F), c'est qu'il y a un problème --> vous n'obtiendrez sans doute pas un dump valide en passant aux étapes suivantes --> même remarque que précédemment, essayez avec le script d'Ulysse qui utilise une autre méthode pour rechercher les call asprotects: 3bis_ASProtect 2.xx_call-fixer2 spass.txt

si tout est ok, on dumpe avec lordpeon fixe les imports sur le dump avec imprec

et ça marche:

III) METHODE PNLUCKvoilà ou vous pouvez télécharger le script original:http://www.tuts4you.com/request.php?2103

j'ai légèrement corrigé le script de départ --> j'ai remplacé les instructions:

eval "Call dword ptr [{data_sect}]"asm x_addr,$RESULT

par

mov [x_addr],0015FF ; on remplace le call asprotect par un call [api]mov [x_addr+02],$RESULT ; on modifie la suite du jmp [api] avec l'adresse de l'IAT

car l'instruction asm ne semble pas supporter les call / jmp indirects sur ma version de odbgscriptvoici donc le script corrigé: 4_ASProtect_2.0x_Fix_IAT_with_Import_Elimination_Optimized.txtJ'ai aussi supprimé la partie du script qui s'exécute quand l'IAT n'est pas corrompue

//copyright by Pnluck 20005 [email protected]// modifie par mars

var $STDvar x_addr //addr originalevar x_LoadLib //addr LoadLibraryAvar x_AddrApivar data_sectvar end_datavar x_eaxvar govar xvarvar strvar xvar str_eaxvar str_edivar sav_eaxvar sav_ecxvar sav_edxvar sav_ebxvar sav_espvar sav_ebpvar sav_esivar sav_edivar save_datavar confrontavar iat_sectionvar save_dllvar OEP

var save_iatsvar save_iate

var prevcallvar calldest

var checkaddvar endaddvar fincallvar Call_Jump

//chiedo l'addr della .data section

reset:mov OEP,eipmsgyn "Is the IAT of this PE corrupt?"cmp $RESULT,0je start_stdgmi eip,CODEBASEmov prevcall, $RESULTask "Enter the address of section where is the IAT:"mov iat_section,$RESULTmov xvar,$RESULTmov str,1500eval "IAT Corrupt: Yes, Code section: {prevcall}, IAT section: {iat_section}, Is this correct?"msgyn $RESULTcmp $RESULT,0je resetmsgyn "Is it CALL to CALL?"cmp $RESULT,0je iniziomov Call_Jump,1

//find the start of iatinizio: On cherche le debut de l'IAT. On commencemov x,[iat_section] notre recherche en 640000 (debut de section IAT)cmp x,0 et tant que les dword sont à zero, on avanceje do_jmp quand le dword est différent de zero, on regardegn x si c'est une api valide --> si c'est pas le cas, on cmp $RESULT_1,0 remplace le dword par un dword nul avec l'instructionjne trovato1 mov [iat section], 0 ; si c'est le cas, on sauve le mov [iat_section],0 début de l'IAT dans save_iats (s comme start)

do_jmp:add iat_section,4jmp inizio

trovato1:mov save_iats,iat_sectioneval "The iat start at {iat_section}"MSG $RESULT

//find the end of iatmov iat_section,stradd iat_section,xvar On cherche la fin de l'IAT. On commence notreinizio1: recherche en 641500 (début de section IAT + 1500) et cette mov x,[iat_section] fois ci, on recule --> ant que les dword sont à zero, on reculecmp x,0 quand le dword est différent de zero, on regardeje do_jmp1 si c'est une api valide --> si c'est pas le cas, on gn x remplace le dword par un dword nul avec l'instructioncmp $RESULT_1,0 mov [iat section], 0 ; si c'est le cas, on sauve la jne pre_start fin de l'IAT dans save_iate (e comme end)

mov [iat_section],0

do_jmp1:sub iat_section,4jmp inizio1

pre_start:mov save_iate,iat_sectionadd iat_section,4mov data_sect,iat_section

//ora cancello dall'iat gli addr erratierase_garbage: quand on connait le début de l'IAT (save_iats)mov x,[save_iats] et la fin de l'IAT (save_iate), on parcoure l'IATgn x en commençant par le début et on remplace lescmp $RESULT_1,0 dword par des dword nuls quand l'api n'est pas validejne add_addr (on efface les api cryptées en fait)mov [save_iats],0add_addr:cmp save_iats,save_iateje getcalladd save_iats,4jmp erase_garbage

getcall:ask "Enter the AIP Call destination address:"mov endadd,$RESULTask "Enter the address of the last call to repair:"mov fincall,$RESULTjmp start_procs

start_procs:eval "AIP call destination: {endadd}, Final call: {fincall}. Is this correct?"msgyn $RESULTcmp $RESULT,1jne getcall

start_proc://domando che call devo analizzareadd prevcall,1cmp prevcall, fincallja finefind prevcall, #e8????????#cmp $RESULT,0je finemov prevcall,$RESULTmov x_addr,$RESULT mov eip,$RESULTmov checkadd,eipadd checkadd,1mov calldest, [checkadd]add calldest, eipadd calldest,5cmp calldest,endaddjne start_procGPA "LoadLibraryA","kernel32.dll"cmp $RESULT,0

je exitmov x_LoadLib,$RESULTadd x_LoadLib,bbp x_LoadLib //setto bp al je di LoadLibraryArunbc x_LoadLib//al bp//verifico secon i egistri è tutto a postocmp eax,0je vuoi_uscicmp edi,0je vuoi_uscimov x_eax,eaxmov str,""mov go,1

//inizio della proc hex->asciianalize:mov xvar,[x_eax]and xvar,0ff

cmp xvar,0je fin_an

GPA [edi],[eax]

mov x,$RESULT

//inizio la ricerca di un dword usabilestart_trovo:cmp save_dll,[eax] on compare la dll de l'api qu'on vient de trouverje trovato à la dll de l'api qu'on venait de trouver précédemmentadd data_sect,4 si les dll sont différentes, on rajoute un dword pour mov save_dll,[eax] espacer les deux dll, sinon on continue à la suite.jmp trovato on sauve l'api du call asprotect a la suite de l'IAT

on remplace le call asprotect par un jmp [api]trovato: on renseigne la suite du jmp [api] avec l'adresse de l'apimov [data_sect],x dans l'IATtrov:mov [x_addr],0025FF mov [x_addr+02],data_sect

mov eip,x_addradd data_sect,4jmp start_proc

fine:mov eip,OEPret

exit:MSG "Error" ret

Pour savoir si un dword non nul de l'IAT contient une api, le script utilise l'instruction gn:

GN addr-------Gets the symbolic name of specified address (ex the API it poits to)Sets the reserved $RESULT variable to the name. If that name is an API$RESULT_1 is set to the library (ex kernel32) and $RESULT_2 to the name of the API (ex ExitProcess).Example:

gn 401000

Ainsi, si on execute l'instruction gn 7C001020, si cette adresse correspond au début d'une api, on aura$RESULT_1 qui contiendra le nom de la dll et $RESULT_2 qui contiendra le nom de l'api.Si cette adresse ne correspond pas à une api $RESULT_1 et $RESULT_2 seront à zéro.

EXECUTION DU SCRIPT

On execute le script à l'OEP --> on mets un bp hardware on execution en 632724, F9 et on lance le script4_ASProtect_2.0x_Fix_IAT_with_Import_Elimination_Optimized.txt

après 15 bonnes minutes (c'est long...) , le script s'arrête:(pour que ça aille plus vite, réduisez la taille de la fenetre d'ollydbg)

on dumpe, on fixe les imports sur le dump avec imprec:

Il y a un import invalide à l'adresse 641058 --> la valeur 58B565DF n'est pas reconnue comme l'adresse d'une api. En réalité, ce script utilise l'api hooking sur loadlibrary et l'instruction GPA [edi],[eax] pour retrouver les apis --> il a donc le même probleme que le script 3_call fixer jmp indirect pour IAT complète ou reconstruite.txt --> il s'agit de l'api "InitCommonControls" de la dll "comctl32.dll"

on double clic sur l'import invalide et on sélectionne notre api dans la liste:

--> on cliques sur fix dump... et ça marche:

EXPLICATION DU SCRIPT

Ce script remplace les call asprotects par des jmp [api] indirects pointant sur l'IAT (comme pour le script script 3_call fixer jmp indirect pour IAT complète ou reconstruite.txt)

la ou c'est différent par rapport a la METHODE ULYSSE_31, c'est au niveau du traitement de l'IAT --> ce script remplace les api non décodées de notre IAT par des dword nuls et ajoute tous les api des call asprotect à la suite de l'IAT

IAT A L'OEP

006402E4 7C92188A ntdll.RtlDeleteCriticalSection --> debut IAT006402E8 7C9110ED ntdll.RtlLeaveCriticalSection006402EC 7C911005 ntdll.RtlEnterCriticalSection006402F0 7C809FA1 kernel32.InitializeCriticalSection006402F4 7C809B14 kernel32.VirtualFree006402F8 7C809A81 kernel32.VirtualAlloc006402FC 7C80995D kernel32.LocalFree00640300 7C8099BD kernel32.LocalAlloc00640304 7C8092AC kernel32.GetTickCount00640308 4AE84D5F --> IAT cryptée0064030C 7C8114AB kernel32.GetVersion00640310 9B6B1A52 --> IAT cryptée00640314 7C809794 kernel32.InterlockedDecrement..................00640E4C 778EB29D setupapi.SetupDiDestroyDeviceInfoList00640E50 778EC415 setupapi.SetupDiEnumDeviceInfo00640E54 778ED64F setupapi.SetupDiGetClassDevsA --> fin IAT

IAT APRES EXECUTION DU SCRIPT PNLUCK

006402E4 7C92188A ntdll.RtlDeleteCriticalSection --> debut IAT006402E8 7C9110ED ntdll.RtlLeaveCriticalSection006402EC 7C911005 ntdll.RtlEnterCriticalSection006402F0 7C809FA1 kernel32.InitializeCriticalSection006402F4 7C809B14 kernel32.VirtualFree006402F8 7C809A81 kernel32.VirtualAlloc006402FC 7C80995D kernel32.LocalFree --> les api cryptées sont netoyees00640300 7C8099BD kernel32.LocalAlloc00640304 7C8092AC kernel32.GetTickCount00640308 00000000 --> IAT netoyée0064030C 7C8114AB kernel32.GetVersion00640310 00000000 --> IAT netoyée00640314 7C809794 kernel32.InterlockedDecrement..................00640E4C 778EB29D setupapi.SetupDiDestroyDeviceInfoList00640E50 778EC415 setupapi.SetupDiEnumDeviceInfo00640E54 778ED64F setupapi.SetupDiGetClassDevsA00640E58 00000000 00640E5C 7C811069 kernel32.GetFileType --> toutes les api des call00640E60 7C810C8F kernel32.GetFileSize asprotect sont rajoutees à 00640E64 7C812CA9 kernel32.GetStdHandle la suite de l'IAT..................00641084 77DA7B76 advapi32.GetTokenInformation --> fin IAT

à noter que pnluck a écrit aussi ce petit tut sympa sur asprotect et AIP:https://quequero.org/uic/pnluck_asproske2.html

CONCLUSIONJe tiens aussi à vous parler d'un dernier script pour unpacker notre cible --> il s'agit de 5 _Aspr2.XX_unpacker v1.0E_Volx.txt, un script de Volx --> vous exécutez ce script à l'EntryPointet en moins d'une minute, les call asprotects sont fixés, et un dump réalisé (de_stpass.exe)Il reste plus qu'a fixer les imports avec les informations disponibles dans le log:

on ouvre imprec et on rempli comme ceci:

On cliques sur Fix Dump et on selectionne de_stpass.exe --> imprec nous crée un fichier de_stpass_.exe totalement valide. Ce script est très rapide car il copie en réalité un call fixer en asm dans la cible. La méthode utilisée pour restaurer l'IAT doit être celle d'Ulysse_31 car les fichiers sont identiques.Je conseille ce script à tout ceux qui ont du mal à venir à bout d'une cible asprotectée (des versions plus récentes sont disponibles sur internet)

Voila, ce tutorial est terminé --> J'espère qu'il vous aura appris quelque chose, et que cela vous aura donné envie d'aller plus loin dans l'étude d'asprotect...

REMERCIEMENTS

Je remercie particulièrement uLysse_31 sans qui je n'aurai pas eu l'idée et l'envie de m'attaquer à asprotect. Je remercie aussi vivement Sp0ke et Z!PPer qui ont testé mon premier script.Un petit coucou à Kirjo, Tytan, Coolmen, taloche, donald, Dynasty, Baboon et plus généralement tous les membres des forums que je fréquente: xtx, deezdynasty, et forumcrack