mardi 22 juillet 2008

Inject your DLL

Pour continuer mon apprentissage du fabuleux (aheum) monde de Windows, je me suis lancé dans l'implémentation d'un grand classique : l'injection de DLL.

Mon but est donc simple : injecter une bibliothèque dynamique dans un/des process distant(s) ( sans m'occuper de l'utilisation qu'on pourrait en faire ). Pour celà j'ai implémenté différentes techniques "de base" dans un petit tool. J'ai également tripé en essayant de rendre le truc le plus furtif possible, c'est à dire en libérant au maximum la mémoire allouée pour l'injection quand il y en a. Let's start :


1- "Classical method" :

Archi-connue, cette technique consiste à utiliser l'API CreateRemoteThread() qui créé donc un thread dans un process distant (jsuis totally bilingual et oué) en lui donnant l'adresse de LoadLibrary() à laquelle on fournit le nom de la DLL à injecter, qui aura été précédemment écris dans la mémoire du process cible ( VirtualAllocEx() + WriteProcessMemory() ).

La seule difficulté c'était de savoir quand je pouvais libérer cette mémoire allouée pour l'appel ( et gagner un petit peu de furtivité plutot que de laisser le nom de la DLL se balader dans la mémoire ) : il me fallait détecter que l'injection était terminée donc que LoadLibrary() à finie son boulot. Comme j'étais chaud bouillant j'ai commencé à dumper le EIP en boucle et à m'exciter sur les valeurs qu'il pouvait prendre une fois l'injection terminée... Puis j'ai découvert WaitForSingleObject(), qui dixit la doc "Waits until the specified object is in the signaled state or the time-out interval elapses." et pour un thread le "signaled state" c'est "when the thread terminates." c'est dans la poche :-)

A noter qu'il ne faut pas oublier de s'allouer les droits de debug pour pouvoir ouvrir un HANDLE sur n'importe quels process et pouvoir ainsi s'injecter dans tout ce qui bouge.
De plus, cette méthode ne fonctionne que sous Windows NT alors par acquis de conscience j'ai rajouté une fonction isWindowsNT() qui utilise l'API GetVersionEx() pour vérifier que la version de l'OS est ok...

2- "Cave code method"

La technique précédente c'est du tout bon, ca marche bien, mais ça fait appel à l'API surpuissante CreateRemoteThread() qui finalement sert "juste" à faire un appel à LoadLibrary(), et qui peut être facilement repéré par des outils anti-injection (enfin, j'imagine :p ).
Donc une autre idée est de se coder un loader en assembleur qui remplacerait l'appel à CreateRemoteThread(), ce qui nous permettrait de nous injecter en chargeant ce loader dans la mémoire du process distant puis en redirigeant le flux d'exécution dessus.

La principale difficulté en ce qui concerne le loader c'est qu'il contient plusieurs adresses qu'on ne connait pas au moment de la compilation : l'adresse de retour où on retournera après que notre loader ait été exécuté, l'adresse de l'argument de LoadLibrary() (= le nom de la dll qu'on veut mapper) et l'adresse de LoadLibrary() ( même si en pratique on a envie d'hardcodé celle là, il faut pas forget que même entre service pack 2 et 3 sous XP il y a des différences au niveau des adresses de mapping des DLLs, donc si on veut faire un truc un minimum portable c'est pas top).

Cela nous donne :

push 0xFAFAFEFE // return address

pushfd // save the eflags register
pushad // save the registers

push 0xFAFAFEFE // argument of LoadLibrary()
mov eax,0xFAFAFEFE // @ of LoadLibrary()
call eax

popad
popfd

ret

Je fous des 0xFAFAFEFE à la place des trucs que je vais devoir patché et j'oublie pas de sauvegarder les registres avant et de les rétablir après histoire de pas avoir de souci :-)

Le code du loader se trouve donc dans mon process "injecteur" et premier petit problème, comment localiser ce code pour pouvoir le patcher ? Sous GCC y a pas de souci, tu utilises la balise __asm__ et tu fous ton code ASM direct à l'intérieur d'une fonction, l'adresse de la fonction te donnera l'adresse de ton loader et en cadeau bonus, tu peux même déclarer une autre fonction "vide" derrière ce qui te permettra de connaitre la taille de ton loader avec la différence d'adresse. Sauf que voilà, dans le but de m'introduire plus profondément dans la communauté des t4pz je mange des chocapicz et je compile désormais avec VC++ 2008. Et sous ce magnifique compilateur que trouve t'on à l'adresse de la fonction dans laquelle on a mis le code ASM du loader ? Je te le donne en mille émile :

00401005 JMP main.00401060

Oui dude, un JMP vers le "vrai" code de la fonction, ce qui est emmerdant. Donc la seule solution c'est de transformer mon loader en opcodes et de mettre tout ça dans une variable globale qui me donnera l'adresse dont j'ai besoin.

Donc une fois que l'adresse du loader est connue, on commence par patcher l'adresse de l'argument de LoadLibrary() qu'on obtient en allouant de la mémoire dans le process cible avec VirtualAllocEx() puis celle de LoadLibrary() qu'on obtient avec GetProcAddress().

Reste à mettre l'adresse de retour, pour celà on va d'abord suspendre le main thread du process distant, à cet effet je me suis codé une fonction qui récupère le TID de ce thread à partir du PID du process distant, rien de bien compliqué : on fait un snapshot de tous les threads du système avec CreateToolhelp32Snapshot(), on récupère ceux dont le PID owner est le même que le PID de notre process cible. Une fois le TID du main thread récupéré, on créer un HANDLE dessus avec OpenThread() et on suspend le thread avec SuspendThread().

On récupère alors l'EIP avec GetThreadContext(), on l'écrit dans notre loader.. et hop ! notre loader est patché, il ne reste plus qu'à le copier dans la mémoire de notre cible. Une fois que c'est fait on modifie l'EIP pour qu'il pointe vers lui avec SetThreadContext(), on relance le thread et on attend ! Si tout va bien, le process cible va exécuter notre loader, charger la DLL puis revenir là où il était...

Oui mais voilà, on attend un peu trop longtemps... Tout est en place, l'EIP pointe bien sur notre loader mais pourtant le code ne s'exécute pas ou du moins pas tout de suite.. à moins que je "passe la souris dessus" ( pour un process graphique j'entend, pour un process console c'est une autre histoire ) . Ce qui n'est pas très pratique, je voudrai pouvoir être sûr que mon code va être exécuté directement une fois que j'ai relancé le thread sans devoir intervenir sur la cible et ainsi pouvoir libérer la mémoire occupé par le loader histoire d'être caché inside the bosquet.

Ma première idée c'était que le problème se situe au niveau de l'ordonnancement : le système ne donne pas de temps processeur à mon process cible tant qu'il n'y pas "quelque chose" qui lui laisse croire qu'il va se passer un truc important dans ce process ( d'où le coup de souris ) ou qu'il n'a rien de mieux à faire. Ce qui est confirmé par le fait que "de temps en temps" l'injection va avoir lieu au bout de 10s et dans d'autres cas, après une minute toujours rien...

Donc j'ai commencé à faire le fou avec les fonctions SetPriorityClass() et SetThreadPriority() histoire de "forcer" le processeur à exécuter mon process cible. Mais j'ai eu beau foutre la priorité max, j'ai vu aucune différence notable... Et j'ai donc abandonné l'idée en me disant que de toute façon l'ordonnanceur Windows doit être un beau bordel et que faudrait être maso ( ou plus fort que moi :p ) pour jouer avec. J'ai quand même retenu cette phrase de la doc qui m'a bien fait rire "Threads are scheduled in a round-robin fashion at each priority level". On y croit.

Finalement, ce que je veux c'est un moyen de simuler mon "coup de souris" sur la cible puisque apparament il n'y a que ça qui force le process à être exécuté ca$h par le processeur à coup sûr. "Me dis pas que dans ces API de malades que Windows possèdent en ce qui concerne les manipulations de processus en user-land, je trouverai pas mon bonheur." Et effectivement, je l'ai trouvé, il suffit d'envoyer un "message" au process qui, comme tout bon GUI process possède une "message queue" et va réagir au quart de tour pour traiter ce message. Pour cela j'utilise PostThreadMessage() qui, dixit la doc, "posts a message to the message queue of the specified thread" (sympa la doc non?). L'effet est immédiat : mon code est exécuté dans la seconde, et je peux enchainer en libérant la mémoire du loader, celle de l'argument de LoadLibrary() et ni vu ni connu jt'injecte :-)

Reste que pour un process console "basique" y a pas de message queue et que donc mon message va tomber dans le vide inter-sidéral et ça ne va rien accélèrer, donc pour ces process là ( que je détecte suivant le code de retour de PostThreadMessage() ) je libère pas la mémoire allouée dans la cible et j'attend sagement que l'injection se fasse. Si quelqu'un connait une fucking way de forcer un process console à être exécuté ca$h par le processeur je lui serai gré de m'en faire part.

3- "SetWindowsHookEx()" :

Là encore, une méthode très connue : tout se joue avec l'API SetWindowsHookEx() qui permet de poser un hook (en gros, une fonction) pour un certain type d'event : si cet event se produit, notre fonction sera appelée. Pour que la fonction soit appelée dans un process distant (= la cible de l'injection) elle dois être définie dans une DLL, lorsque l'event se produit, le process va vouloir exécuter la fonction définie comme étant le hook et pour cela il va charger la DLL qui la contient :-) De plus, on peut définir si on veut poser le hook pour un thread particulier ou pour tous les threads qui sont dans le "same desktop" donc ça nous donne une possibilité d'injection massive ! Intéressons nous d'abord à une attaque "ciblé".

La théorie ça rox, mais en bidouillant un peu on s'aperçoit que cette API est un peu plus fourbe que ça :

1- Première remarque : cette technique ne marche que sur des process "graphiques" et pas pour des process consoles ( décidément... ), en trifouillant sur le net on trouve une explication "Hooks don't work on console processes. The process wich runs consoles (csrss) is considered to be too important to the system so it is designed this way." Pour rendre les choses plus propres on a un moyen de distinguer les process consoles/GUI et éviter ainsi des appels inutiles :

if(WaitForInputIdle(targetHandle,0)==WAIT_FAILED)
{
// console process
}
else
{
// GUI process
}

2- Sur quel évènement poser ce hook ? Il nous en faut un qui nous garantisse une injection le plus rapidement possible une fois le hook mis en place. Je comprend pas vraiment pourquoi la majorité des exemples sur le net se borne à mettre WH_CBT en argument de SetWindowsHookEx() c'est à dire un évènement qui correspond à "activating, creating, destroying, minimizing, maximizing, moving, or sizing a window", en gros un truc qui nous pousse à devoir intervenir sur le process cible avec la souris. En mettant un WH_GETMESSAGE qui lui "Installs a hook procedure that monitors messages posted to a message queue. ", il suffit ensuite de poster un message avec PostThreadMessage() et on est sûr que le hook va avoir lieu...

3- Le hook n'existe que tant que le thread qui le pose est vivant, dès qu'il meurt non seulement le hook n'existe plus ( et donc l'injection ne peut plus avoir lieu ) mais de plus, il va y avoir un FreeLibrary() sur notre DLL dans le thread qui a utilisé ce hook. Là j'avoue ne pas vraiment comprendre pourquoi il y a cet appel, après une petite enquète il est déclenché par un GetMessageW(), j'aurai donc tendance à penser que c'est bien déclenché à distance mais il n'est pas à exclure que ça soit plutôt une erreur dans mon implémentation ;-)
Ca pose en tout cas un léger problème : une fois que le thread qui a posé le hook est mort, le FreeLibrary() qui va avoir lieu dans la cible va décrémenter le compteur de référence de la DLL qui va arriver à 0 (=plus personne n'a besoin de cette bibliothèque) et elle va donc être déchargé de la cible... Un peu emmerdant c'était justement le but de la manoeuvre de la charger :-D
Donc pour bypass ce ptit souci j'ai rajouté dans la fonction de hook dans la DLL un appel à LoadLibrary() sur elle-même, ce qui incrémente le compteur de référence et évite qu'elle soit déchargé lors de la mort du thread injecteur. "C'est moche mais ça marche !"
Vous trouverez le code de la DLL en question ici. Remarquez le très subtil chemin de la DLL rentrer en dur pour l'appel à LoadLibrary()...

4- "The IvanOv m4l4ri4"
Ainsi nommée d'après la personne bien intentionné qui m'en a donné l'idée.

Comme dis précédemment, SetWindowsHookEx() permet de poser un hook sur tous les threads du bureau, ce qui nous donne envie de s'en servir pour injecter tout le monde !
Déjà, dans l'idée de l"IvanOv m4l4ri4", l'évènement qui va déclencher le hook ne sera pas "controlé" par l'injecteur : on va laisser le WH_CBT et dès qu'un process "bougera" il sera infecté (c'est plus rigolol, non ?).

Rappelons nous que le hook n'existe que tant que le thread qui l'a posé est vivant. Or, on a pas envie de laisser tourner notre process injecteur en tache de fond le temps que tous les autres process soient infectés. Pour celà il serait plus judicieux de faire une première injection dans un process dont on est sûr qu'il sera "toujours là" et on laisse ce process poser le hook.

En fait tout va se jouer dans la DLL, on va l'injecter dans notre process qui va servir de pivot à l'infection, puis on va poser le hook sur tout le système dans le DllMain(). Une fois le hook posé, tous les process qui vont recevoir un event graphique vont charger la DLL. Ca nous amène au principal problème de cette technique : les hooks qui concerne tous les threads du bureau sont extrèmement couteux en performance, donc si à chaque chargement de la DLL, le hook est de nouveau posé ça va rapidement faire ramer la machine, pas très discret. Il nous faut donc un moyen de savoir si le hook est déjà poser quand on charge la DLL, histoire de savoir si c'est à nous de le faire ou pas. Ce qu'on veut c'est donc une sorte de "variable globale" au système qui serait en gros mise à 1 si le hook est déjà en place et nous permettrait ainsi une communication inter-processus. Et ça porte un nom : le mutex. Pour faire simple un mutex est lié à un thread qui le "possède", et n'importe quel thread du système peut essayer de prendre possession du mutex en appelant WaitForSingleObject(), il réussira si personne ne l'a pris avant lui (ou si il l'a libéré). Pour le reconnaitre le mutex sera dans notre cas "nommé" c'est à dire qu'on lui donne un nom particulier. Donc tout ce qu'il y a faire lors du chargement de la DLL c'est de faire appel à CreateMutex() avec le nom de notre mutex, si c'est le premier appel le mutex va être créé, puis le thread va faire un WaitForSingleObject() pour en prendre possession, et ainsi poser le hook. Le prochain thread qui "bouge" va utiliser le hook et charger la DLL, ce coup-ci l'appel à CreateMutex() lui retournera juste un HANDLE sur le mutex ( qui existe déjà ) et l'appel à WaitForSingleObject() lui signalera que le mutex est déjà pris par un autre thread, donc que ce n'est pas à lui de poser le hook.
Remarquons que pour faire la première injection, il ne faut pas utiliser la technique 1 qui consiste à créer un thread dans le process cible, car une fois que ce thread va se terminer, le hook va être enlevé et le mutex se retrouvera esseulé (= dans l'état WAIT_ABANDONED, qui est décrit à tord comme un "success state" dans la doc).. La "cave method" est bien plus adapté car elle utilise le main thread ( je savais bien que j'avais pas fait ça pour rien !).
Donc pour cette technique il suffit de lancer l'injection de la DLL dont le code source est ici en "cave method" puis de laisser faire la nature, le premier qui bouge, BAM dans sa geule.

Il faut quand même remarquer que j'ai un souci au niveau de la libération du mutex, qui doit se faire lorsque le process "pivot" de l'injection meurt, donc quand il décharge la DLL. J'ai pas réussi à faire marcher un ReleaseMutex() dans la clause DLL_PROCESS_DETACH de ma DLL. C'est pas vraiment grave dans le sens où ça empeche juste la technique de fonctionner une deuxième fois de suite avec un mutex de même nom ( le mutex est en WAIT_ABANDONED : le thread qui le possèdait est mort et ne l'a pas libéré ). Mais si votre infection a bien fonctionné, y a normalement pas besoin de la relancer :-)

Le code final est ici.

lundi 14 juillet 2008

Debug me !

Dans la série des useless tools...

Après l'avoir vu utilisé dans différents articles (notamment le très bon post de YoLeJedi sur nibbles ou encore par Ivan dans son implémentation de PaX) j'ai décidé de me coder un petit débuggeur tranquillou mon pépère.
Mon idée est classique : c'est de permettre deux modes : l'un intrusif et l'autre pas. Késako ?

I- Be bad

Le mode intrusif c'est du classique, on créé un processus en mode debug et on va donc recevoir tous les debugEvent qui vont se produire ( chargement des DLLs, exceptions...) ce qui peut être bien pratique pour fuzzer : on lance une application en boucle avec différentes entrées et on récupère les exceptions pour voir où ça merde. Côté réalisation y a rien de bien compliqué, surtout que la doc est assez explicite sur ce point: après la création du process ( avec l'argument DEBUG_PROCESS ) on rentre dans une debug loop qui va catcher tout les debugEvent et c'est du tout bon.

II- Be bad ok, but without pain please


Le mode non-intrusif est plus fallacieux. Déjà, quel est son intéret ? Il est simple : un process peut avoir des comportements différents suivant qu'il est sous le contrôle d'un débuggeur ou non. Sans rentrer dans des techniques anti-debugging complexes on peut citer deux exemples "naturels" : le gestionnaire d'exception final ( aka UnhandledExceptionFilter ) qu'on peut mettre en place dans un programme avec SetUnhandledExceptionFilter() n'est pas appelé en mode debug, ce qui n'est pas très pratique quand on veut tester une exploitation qui consiste justement à réécrire son adresse. On peut aussi parler des chunks dans le tas qui sont différents en mode débug (16 bytes de plus), ce qui facilite pas les choses lors du test d'un heap overflow ;-)

L'idée du mode non-intrusif consiste à s'attacher au process une fois qu'il est lancé avec l'API DebugActiveProcess(). J'ai alors crié "This is just so fuking easy !" : je créé mon process avec un CreateProcess() en mode normal, j'apelle cette API pour m'attacher puis je lance ma debug loop et hop.

Je me suis lancé, j'ai codé, j'ai compilé, j'ai executé et jme suis pris un comportement bien bizarre dans la gueule : pour la majorité des process auquels je m'attache ça plante complètement, pour d'autres ça marchent, mais pas à tous les coups et dans certains cas, chose bizarre, j'arrive à récupérer le nom des DLLs qui sont chargés (après l'avoir lancé une première fois) alors que ça ne devrait pas être le cas...

Après avoir consulté l'oracle (aka YoLeJedi :p), j'ai compris ce qui se passait. Pour celà, intéressons nous au processus de création d'un process sous Windows (par l'appel à CreateProcess()) : il se divise en 6 étapes (extrait de Windows Internals) :

  1. Open the image file (.exe) to be executed inside the process.

  2. Create the Windows executive process object.

  3. Create the initial thread (stack, context, and Windows executive thread object).

  4. Notify the Windows subsystem of the new process so that it can set up for the new process and thread.

  5. Start execution of the initial thread (unless the CREATE_ SUSPENDED flag was specified).

  6. In the context of the new process and thread, complete the initialization of the address space (such as load required DLLs) and begin execution of the program


Comme on peut le voir c'est assez complexe, et c'est là que se situe le problème, si juste après l'appel à CreateProcess() j'apelle DebugActiveProcess(), je risque de m'attacher avant que la fin de l'initialisation réalisé à l'étape 6 ne soit terminée. Or DebugActiveProcess() est une API qui est faite pour s'attacher à des process "en cours" donc complètement initialisé. Ca explique aussi pourquoi desfois je reçois les noms des DLLs : si je m'attache avant l'étape 6 alors je serai "notifié" du chargement des DLLs comme si le process avait été lancé en mode DEBUG ( m'enfin je m'avance peut être un peu là ).
Tout ce qu'il faut retenir c'est qu'il faut laisser le processus d'initialisation se terminer avant de s'attacher.

Une première idée pourrait être de caler un ptit Sleep(X) avant de faire appel à DebugActiveProcess() : ça fonctionne mais c'est moche : si la durée d'endormissement est trop longue le process qu'on débugge aura déjà attaquer d'exécuter son code et donc on risque de louper des debugEvents ( et c'est bien sûr pas portable du tout, si un process charge beaucoup de DLLs il mettra plus de temps à s'initialiser et hop il faudra changer la valeur ).

Il existe une meilleure façon de faire, in 6 steps :

1- On créé le process en mode SUSPENDED : il est suspendu (jure?)...
2- On modifie l'Entry Point (EP) du programme pour y mettre une boucle infinie
3- On relance le process et on attend que le main thread (créé à l'étape 5) du programme atteigne l'EP ( avant d'y arriver il réalise l'étape 6 de l'initialisation, c'est à dire le chargement des DLLs et tout le bordel) : le process est alors complétement initialisé et se met à boucler.
4- On s'attache au process avec DebugActiveProcess() .
5- On restaure l'EP initial, le main thread commence l'execution du code et on n'en perd pas une miette :-)

Décrivons plus en détail les étapes les plus compliquées (pas trop non plus :p) :

2- Il nous faut récupérer l'EP, pour celà jme fait un petit parcours du header PE. Les lecteurs assidus de ce blog (et ils sont nombreux, aheum), auront remarqué que mon premier useless tool était justement un PE reader, cool, je peux réutiliser mon code ?! En fait pas du tout, pour faire plaisir au ch3f je mappe le fichier en mémoire avec MapViewOfFile() et je le parcours directement en déréférençant des pointeurs.
2-1 Une fois l'adresse de l'EP récupérée, on utilise VirtualProtectEx() pour changer la protection de la mémoire où il se trouve et pour pouvoir y écrire.
2-2 On oublie pas de faire une sauvegarde des deux premiers octets se trouvant à l'EP
2-3 On écrit EB FE à la place de ces octets ce qui correspond aux opcodes d'un JMP à la même addresse, donc une boucle infinie :). On cale un ptit appel à FlushInstructionCache() derrière pour s'assure que nos modifs se propageront bien en mémoire centrale et ne resteront pas dans le cache du processeur.

3- On relance le processus avec ResumeThread(), et on attend qu'il atteigne l'EP en dumpant la valeur de l'EIP avec GetThreadContext().

4- Appel à DebugActiveProcess()..

5- On réécrit les anciens opcodes à la place de notre boucle infinie, on oublie pas de flusher et de restaurer les anciens droits de la mémoire.

Let's launch the debug loop, et c'est good :-)


III- Et comment tu t'apelles ?

Comme YoLeJedi l'explique dans son post, dans le monde non intrusif, on n'a pas accès aux noms des DLLs loadés en mémoire directement dans les structures de debug qu'on récupère. Pour trouver ces noms il faut se casser un peu le c..
YoLeJedi donne 3 techniques pour le faire, perso j'ai implémenté la première qui, dixit le maitre, est la plus rapide :-)
"Le principe consiste à mapper le premier octet du fichier en mémoire à partir de son Handle (CreateFileMapping + MapViewOfFile). Ceci crée indirectement un objet section qui contient le chemin du fichier. Celui-ci est alors récupéré avec l’API GetMappedFileName. La lettre du lecteur est retrouvée avec le couple GetLogicalDriveStrings + QueryDosDevice."
Et on trouve un code source qui nous fait tout ça ici.

Mais comme ce code est un peu trop propre et que le copier/coller favorise pas l'apprentissage, je me suis retapé un codage "maison" que vous trouverez dans la fonction getTheNameOfTheFile(), c'est pas vraiment joli (voire carrément dégeulasse) et sûrement pas assez commenté mais ca reste du parcours de string, donc pas très complexe à refaire :)

Vous trouverez donc la source du bouzin ici ( pas oublié de linker avec Psapi.lib lors du build de l'executable pour GetMappedFileName() ) et le binaire compilé sous XP SP2 .

Une prochaine amélioration pourrait être un mode interactif pour pouvoir afficher la mémoire du process avec des commandes à la gdb...

Thanks to YoLeJedi pour son aide et ses commentaires utiles ;-)

lundi 7 juillet 2008

Useless tool...

Inauguration d'une nouvelle section de ouf qui regroupera les tools inutiles que je code...

First entry : un PE reader qui a rien de bien original si ce n'est que j'essaye de restreindre l'affichage aux champs intéressants. Il peut être facilement customizé pour n'afficher que le header qui vous intéresse.
J'ai aussi rajouter ma section cop4ain, donc si t'es mon copin et que je t'ai oublié, query me on irc ;-)