Archive for janvier, 2009

Playing with DynamoRio

Et non ce blog n’est pas le mort, depuis le temps que je l’ai délaissé, je commençais à m’en vouloir. Disons que j’ai été prit par mal de taff en plus de pas faire grand chose de bien au niveau de mes projets perso et puis il y a eu une pénurie de chocapicz à cause de la crise :( Alors au lieu de poster pour rien dire d’intéressant ou même de la merde comme une grosse tapz, j’ai préféré fermer ma gueule. Ce post est consacré à une technologie récente mais encore trop peu connue par les gens qui font des vrais choses (comprendre : pas des projets java et du .Net) : la Dynamic Binary Instrumentamention (DBI).

Pour illustrer mes propos je vais me baser sur le framework DynamoRio, développé par le Mit et les laboratoires HP, récemment acquit par VmWare. La dernière version est disponible sur le site govirtual.org, un site dédié à la recherche en virtualisation. La première fois ou j’ai découvert le DBI fut en lisant le paper de Skape ‘Memalyze: Dynamic Analysis of Memory Access Behavior in Software‘ (et aussi ). Il existe d’autres frameworks permettant de faire du DBI comme PIN qui est notamment utilisé par l’outil Saffron présenté par Danny Quist et Valsmith durant la conf à BH2K7 Covert Debugging. Un des outils les plus connu qui fonctionne avec du DBI est Valgring.

Maintenant passons aux choses sérieux. Le DBI c’est quoi ? On pourrait le voir comme une machine virtuelle orientée analyse de code et instrumentation de code. Quand je dis instrumentations c’est à des fins d’analyses, de tracing et de mesure de performances ainsi que de modification en temps réel du code. En gros le code d’un thread est passé dans une moulinette qui offre différentes actions possible sur ce dernier. L’outil de DBI va se placer à des endroits stratégiques, en général aux branchements, pour contrôler le flux d’exécution. Attention en aucun le DBI ne va pas faire de l’émulation du code (sauf cas particuliers), le code est toujours exécuté nativement sur le processeur.

Dans le cadre de DynamoRio, celui-ci se présente sous la forme d’une librairie, dynamorio.dll, qui va surveiller les actions importantes d’un processus et de ses threads pour appliquer son instrumentation. Sous Windows, c’est une DLL, dynamorio.dll qui se retrouve injecté dans un processus au moment de son initialisation. Cette DLL est chargée par drpreinject.dll qui se retrouve injecté dans tous les process grâce à la clé HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs. Celle clé contient le path de drpreinject.dll. drpreinject.dll va ensuite consulter les infos stockée dans HKEY_LOCAL_MACHINE\SOFTWARE\VMware,Inc.\DynamoRIO\ pour savoir si elle doit charger dynamorio.dll. Cette clé contient des dossiers nommés par des process. Chez moi j’ai pas exemple dans la clé stockée dans HKEY_LOCAL_MACHINE\SOFTWARE\VMware,Inc.\DynamoRIO\test.exe les valeurs :

DYNAMORIO_AUTOINJECT : C:\ProgHack\c\dynamorio\lib32\debug\dynamorio.dll
DYNAMORIO_LOGDIR : C:\ProgHack\c\dynamorio\logs
DYNAMORIO_OPTIONS : -code_api -no_hide -client_lib "C:\ProgHack\c\dynamorio\me\dyn.dll;0;"
DYNAMORIO_RUNUNDER : 1

Pour enregistrer un binaire auprès de DynamoRio on utilise l’outil drdeploy.exe en faisant (j’expliquerais cette ligne de commande plus en détail par le suite) :

drdeploy.exe -reg test.exe -root C:\ProgHack\c\dynamorio\ -ops "-no_hide" -client C:\ProgHack\c\dynamorio\me\dyn.dll 0 ""

Par défaut dynamorio.dll se cache de la liste de modules chargés (unlikage dans les doubles listes chainées : InLoadOrderModuleList InMemoryOrderModuleList et InitializationOrderModuleList du champ Ldr de type PEB_LDR_DATA du PEB) pour désactiver cela on utilise l’option -no_hide. L’analyse du code commence quand à elle après le call à dynamorio!dynamorio_app_take_over par drpreinject.dll. Le premier code analysé n’est pas celui de notre binaire mais celui de kernel32!FreeLibrary appelé par DynamoRio pour décharger drpreinject.dll.

Ca c’était pour la partie Windows et injection de DLL. Commençons à parler du DynamoRio lui même. J’ai eu du mal à prendre la doc en main (même si c’est moins difficile que celle de metasm) avec l’aide de tutoriaux, ce n’est pas évident. J’ai tout de même réussit à comprendre une bonne partie des fonctionnalités.

La librairie va commencer à analyser le code sous forme de blocs, ces blocs sont presque de la même forme que ceux générés par l’analyse de IDA. Un bloc est une séquence d’instructions se terminant par une 1 unique instruction de branchement : jmp, jcc, call ou ret. Par exemple, les instructions suivantes :

(Le dissass a été effectué avec l'API de DynamoRio)
TAG  0x7c925e4a
 +0    L3  8b ff                mov    %edi -> %edi
 +2    L3  55                   push   %ebp %esp -> %esp (%esp)
 +3    L3  8b ec                mov    %esp -> %ebp
 +5    L3  83 7d 08 00          cmp    0x08(%ebp) $0x00000000
 +9    L3  0f 85 82 45 03 00    jnz    $0x7c95a3db
 +15   L3  8b 4d 0c             mov    0x0c(%ebp) -> %ecx
 +18   L3  85 c9                test   %ecx %ecx
 +20   L3  56                   push   %esi %esp -> %esp (%esp)
 +21   L3  57                   push   %edi %esp -> %esp (%esp)
 +22   L3  74 33                jz     $0x7c925e95
 +24   L3  66 8b 39             data16 mov    (%ecx) -> %di
 +27   L3  0f b7 c7             movzx  %di -> %eax
 +30   L3  6a 02                push   $0x00000002 %esp -> %esp (%esp)
 +32   L3  99                   cdq    %eax -> %edx %eax
 +33   L3  5e                   pop    %esp (%esp) -> %esi %esp
 +34   L3  f7 fe                idiv   %esi %edx %eax -> %edx %eax
 +36   L3  85 d2                test   %edx %edx
 +38   L3  75 32                jnz    $0x7c925ea4
 +40   L3  66 8b 71 02          data16 mov    0x02(%ecx) -> %si
 +44   L3  53                   push   %ebx %esp -> %esp (%esp)
 +45   L3  0f b7 c6             movzx  %si -> %eax
 +48   L3  6a 02                push   $0x00000002 %esp -> %esp (%esp)
 +50   L3  99                   cdq    %eax -> %edx %eax
 +51   L3  5b                   pop    %esp (%esp) -> %ebx %esp
 +52   L3  f7 fb                idiv   %ebx %edx %eax -> %edx %eax
 +54   L3  5b                   pop    %esp (%esp) -> %ebx %esp
 +55   L3  85 d2                test   %edx %edx
 +57   L3  75 1f                jnz    $0x7c925ea4
 +59   L3  66 3b fe             data16 cmp    %di %si
 +62   L3  77 1a                jnbe   $0x7c925ea4
 +64   L3  66 85 ff             data16 test   %di %di
 +67   L3  74 0e                jz     $0x7c925e9d
 +69   L3  83 79 04 00          cmp    0x04(%ecx) $0x00000000
 +73   L3  74 0f                jz     $0x7c925ea4
 +75   L3  33 c0                xor    %eax %eax -> %eax
 +77   L3  5f                   pop    %esp (%esp) -> %edi %esp
 +78   L3  5e                   pop    %esp (%esp) -> %esi %esp
 +79   L3  5d                   pop    %esp (%esp) -> %ebp %esp
 +80   L3  c2 08 00             ret    $0x00000008 %esp (%esp) -> %esp
 +83   L3  3b c7                cmp    %eax %edi
 +85   L3  0f 8c a9 00 00 00    jl     $0x7c925e36
 +91   L3  56                   push   %esi %esp -> %esp (%esp)
 +92   L3  57                   push   %edi %esp -> %esp (%esp)
 +93   L3  e8 b6 00 00 00       call   $0x7c925e4a %esp -> %esp (%esp)
END 0x7c925e4a

Seront transformées par les blocs :

TAG  0x7c925e4a
 +0    L3  8b ff                mov    %edi -> %edi
 +2    L3  55                   push   %ebp %esp -> %esp (%esp)
 +3    L3  8b ec                mov    %esp -> %ebp
 +5    L3  83 7d 08 00          cmp    0x08(%ebp) $0x00000000
 +9    L3  0f 85 82 45 03 00    jnz    $0x7c95a3db
END 0x7c925e4a

TAG  0x7c925e59
 +0    L3  8b 4d 0c             mov    0x0c(%ebp) -> %ecx
 +3    L3  85 c9                test   %ecx %ecx
 +5    L3  56                   push   %esi %esp -> %esp (%esp)
 +6    L3  57                   push   %edi %esp -> %esp (%esp)
 +7    L3  74 33                jz     $0x7c925e95
END 0x7c925e59

TAG  0x7c925e62
 +0    L3  66 8b 39             data16 mov    (%ecx) -> %di
 +3    L3  0f b7 c7             movzx  %di -> %eax
 +6    L3  6a 02                push   $0x00000002 %esp -> %esp (%esp)
 +8    L3  99                   cdq    %eax -> %edx %eax
 +9    L3  5e                   pop    %esp (%esp) -> %esi %esp
 +10   L3  f7 fe                idiv   %esi %edx %eax -> %edx %eax
 +12   L3  85 d2                test   %edx %edx
 +14   L3  75 32                jnz    $0x7c925ea4
END 0x7c925e62

TAG  0x7c925e72
 +0    L3  66 8b 71 02          data16 mov    0x02(%ecx) -> %si
 +4    L3  53                   push   %ebx %esp -> %esp (%esp)
 +5    L3  0f b7 c6             movzx  %si -> %eax
 +8    L3  6a 02                push   $0x00000002 %esp -> %esp (%esp)
 +10   L3  99                   cdq    %eax -> %edx %eax
 +11   L3  5b                   pop    %esp (%esp) -> %ebx %esp
 +12   L3  f7 fb                idiv   %ebx %edx %eax -> %edx %eax
 +14   L3  5b                   pop    %esp (%esp) -> %ebx %esp
 +15   L3  85 d2                test   %edx %edx
 +17   L3  75 1f                jnz    $0x7c925ea4
END 0x7c925e72

TAG  0x7c925e85
 +0    L3  66 3b fe             data16 cmp    %di %si
 +3    L3  77 1a                jnbe   $0x7c925ea4
END 0x7c925e85

TAG  0x7c925e8a
 +0    L3  66 85 ff             data16 test   %di %di
 +3    L3  74 0e                jz     $0x7c925e9d
END 0x7c925e8a

TAG  0x7c925e8f
 +0    L3  83 79 04 00          cmp    0x04(%ecx) $0x00000000
 +4    L3  74 0f                jz     $0x7c925ea4
END 0x7c925e8f

TAG  0x7c925e95
 +0    L3  33 c0                xor    %eax %eax -> %eax
 +2    L3  5f                   pop    %esp (%esp) -> %edi %esp
 +3    L3  5e                   pop    %esp (%esp) -> %esi %esp
 +4    L3  5d                   pop    %esp (%esp) -> %ebp %esp
 +5    L3  c2 08 00             ret    $0x00000008 %esp (%esp) -> %esp
END 0x7c925e95

TAG  0x7c925d85
 +0    L3  3b c7                cmp    %eax %edi
 +2    L3  0f 8c a9 00 00 00    jl     $0x7c925e36
END 0x7c925d85

TAG  0x7c925d8d
 +0    L3  56                   push   %esi %esp -> %esp (%esp)
 +1    L3  57                   push   %edi %esp -> %esp (%esp)
 +2    L3  e8 b6 00 00 00       call   $0x7c925e4a %esp -> %esp (%esp)
END 0x7c925d8d

Ces blocs sont mit en mémoire dans le ‘basic bloc cache’. Leur code est ensuite exécuté par DynamoRio qui se charge soit de crée un nouveau bloc si celui n’existe pas soit de crée un bloc plus gros constitué de blocs déjà existant. Ce second type d’exécution va donc crée des nouveau blocs à partir d’anciens, ces nouveaux blocs sont mit en mémoire dans le ‘trace cache’. DynamoRio améliore ainsi les performances car il n’a plus besoin d’effectuer lui même les transitions entre ces blocs, cela est souvent utilisé lorsqu’il y a des boucles dans le code. Avec un schéma ca donne ça :
bloc_cache

Attentions, je précise bien que le binaire va être exécuté à partir de ces blocs et non plus depuis le code original !

C’est sur ces blocs que DynamoRio propose à l’utilisateur d’agir, il peut par exemple simplement les analyser ou y ajouter/modifier/changer des instructions. En fait si on prend l’architecture de DynamoRio :
flow

Le client n’a accès qu’aux fonctions fournies par la partie supérieure du schéma. Il ne s’agit pas vraiment de fonction mais plus précisément d’events qui seront notifiés au client sous forme de callbacks. La partie client vient s’interfacer avec la DLL dynamiorio.dll présente en mémoire. Le client se retrouve aussi sous la forme d’une DLL qui est chargée cette fois ci par dynamiorio.dll. Le path de cette DLL est passé par le paramètre -client_lib de drdeploy.exe. Le client possède une fonction d’initialisation qui va être appelé par dynamorio pour mettre en place les différents callbacks. Voici le client le plus minimaliste que l’on puisse faire :

#include <windows.h>
#include "dr_api.h"

void event_exit(void)
{
    // empty client
}

DR_EXPORT void dr_init(client_id_t id)
{
    // empty client
    dr_register_exit_event(event_exit);
}

La fonction dr_init de notre DLL est ainsi appelée par dynamorio, c’est dans cette fonction qu’on place l’enregistrement de nos différentes callbacks. En voici les 2 principales :

  • dr_register_bb_event() : Enregistre une callback qui est appelée après la création de chaque bloc.
  • dr_register_trace_event() : Enregistre une callback qui est appelé après la création d’un bloc dans le trace-bloc. Lorsque des blocs sont ajoutés dans un trace-bloc, la routine qui gère les basics-bloc est rappelée avec le paramètre for_trace à true. Cela permet de savoir si un bloc va être ajouté dans un trace-bloc. Cela permet d’effectuer des modifications différentes en fonction des 2 cas.

Allons un peu plus loin. DynamoRio propose 2 manières de jouer avec le code au sein des blocs. La première met en avant l’utilisation des meta-instructions, ce sont des instructions de la même forme que les autres sauf quelles ne seront pas exécutées comme faisant partie de l’application mais par dynamorio (elles ne seront pas ajouté dans de nouveau bloc). Il est par exemple possible de mettre en place un « clean-call », qui va appeler notre routine dans le contexte de l’application actuel mais sans en modifier l’état, les meta-instructions travaillant sur une copie du contexte.

Il est aussi possible de manipuler directement le code des blocs, c’est comme si on modifait le code du binaire en mémoire mais depuis les blocs. On ne touche donc toujours pas au code original en mémoire ! Par contre cela pose un problème important dans le cas ou les nouvelles instructions créent une exception. DynamoRio doit savoir comment retomber sur ses pattes. Pour cela on met en place une « translation » allant des nouvelles instructions vers les anciennes. Si une exception intervient dans ces instructions, alors DynamoRio présente à l’application les instructions qui suivent la translation et qui doivent être considérées comme fautives. Par exemple, si je décide de modifier l’instruction : ‘inc dword ptr [ebx]‘ par ‘add dword ptr [ebx], 1′ alors je dis à DynamoRio qu’en cas de faute sur cette nouvelle instruction l’application devra considérer que c’est l’instruction « inc dword ptr [ebx] » qui a faulté. On met en place une translation avec l’API instr_set_translation. Bon, j’avoue, cela n’est pas super évident à comprendre et la doc est pas super clair à ce propos, d’ailleurs je ne suis même pas complètement sur de l’avoir parfaitement compris :(

Toujours dans le cadre d’une exception, DynamoRio propose 2 manières de gérer les translations. Si l’on a renvoyé DR_EMIT_DEFAULT lors d’une des 2 callbacks de créations de blocs, alors cette callback elle rappelée avec le paramètre « translating » à true. Si l’on a renvoyé DR_EMIT_STORE_TRANSLATIONS alors DynamoRio saute cet étage de rappel à la callback du basic-bloc.

Tout ce que j’ai dit plus haut représente les fondements de DynamoRio, bien entendu il existe d’autres fonctionnalités comme des events qui supportent la création de nouveaux threads (dr_register_thread_init_event()) ou le chargement de modules (dr_register_module_load_event()). Cependant avec ces bases, nous sommes en mesure des faire des choses sympas.

On va réaliser un client simple qui va aller hooker l’API native ZwQuerySystemInformation. L’idée est donc de trouver le bloc qui correspond au wrapper de ce syscall dans ntdll. Pour rappel ils sont de la forme :

mov eax,
mov edx, SharedUserData!SystemCallStub
call dword ptr [edx]
ret

Notre hook va posséder la forme d’un inline hook, avec donc un « jmp MyZwQuerySystemInformation » qui se chargera d’appeler la fonction originale puis de filtrer les résultats pour, par exemple, cacher un process.

Le prototype de la callblack que l’on enregistre avec dr_register_bb_event est :
dr_emit_flags_t (*)(void * drcontext, void * tag, instrlist_t * bb, bool for_trace, bool translating);

J’ai déjà expliqué les paramètres for_trace et translating. Tag est un identifiant unique pour le bloc, en fait c’est tout simplement l’adresse de sa première instruction. bb représente la liste des instructions du bloc et drcontext est une structure opaque représentant l’état de l’application. Dans notre callback on va donc récupérer l’adresse virtuelle qui correspond à chacune des instructions du bloc et vérifier si l’une d’entre elles est l’adresse de l’API ZwQuerySystemInformation dans ntdll. Pour cela on fait une boucle while sur la liste des instruction en retrouvant leur adresse avec l’API instr_get_app_pc(). Une fois qu’on a trouvé l’adresse nous créons une instructions de saut sur MyZwQuerySystemInformation avec le code : in_jmp=INSTR_CREATE_jmp(drcontext, opnd_create_pc((app_pc)MyZwQuerySystemInformation)); Enfin on insère cette instruction au début du bloc avec l’API instrlist_prepend. Au final on le code :

dr_emit_flags_t event_block(void * drcontext, void * tag, instrlist_t * bb, bool for_trace, bool translating)
{
	instr_t *instr, *in_jmp, *in_call;
	opnd_t call;

	//dr_fprintf(drout, "event_block | tag : 0x%x | tid : %d | for_trace : %s | translating : %s\n", tag, //dr_get_thread_id(drcontext), for_trace ? "true" : "false", translating ? "true" : "false");
	//instrlist_disassemble(drcontext, tag, bb, drout);

	//
	// We dont care about translation
	//
	if(for_trace)
		return DR_EMIT_DEFAULT;

	instr=instrlist_first(bb);
	while(instr!=NULL)
	{
		//
		// ZwQuerySystemInformation bloc
		//
		if((FARPROC)instr_get_app_pc(instr)==pZwQuerySystemInformation)
		{
			dr_fprintf(drout, "Syscall ZwQuerySystemInformation found !\nReplacing code ...\n");

			in_jmp=INSTR_CREATE_jmp(drcontext, opnd_create_pc((app_pc)MyZwQuerySystemInformation));
			instr_set_translation(in_jmp, instr_get_app_pc(instr));
			instrlist_prepend(bb, in_jmp);

			instrlist_disassemble(drcontext, tag, bb, drout);
			return DR_EMIT_STORE_TRANSLATIONS;
		}

		instr=instr_get_next(instr);
	}

	return DR_EMIT_DEFAULT;
}

Du fait qu’on a modifié le code de l’API ZwQuerySystemInformation (attention uniquement dans le bloc, pas en mémoire je rappel) on ne plus y faire appel dans la fonction MyZwQuerySystemInformation (argh stack-overflow !). On recode donc directement la fonction avec le wrapper de syscall. Ce qui donne :

NTSTATUS __stdcall MyZwQuerySystemInformation(
__in     	SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout 	PVOID SystemInformation,
__in       	ULONG SystemInformationLength,
__out_opt  	PULONG ReturnLength)
{

	dr_fprintf(drout, "Calling ZwQuerySystemInformation\n"
	"SystemInformationClass : 0x%x\n"
	"SystemInformation : 0x%x\n"
	"SystemInformationLength : 0x%x\n"
	"ReturnLength : 0x%x\n",
	SystemInformationClass,
	SystemInformation,
	SystemInformationLength,
	ReturnLength);

	__asm
	{
		pop ebp

		mov eax, 0xAD
		mov edx, 0x7FFE0300
		call dword ptr [edx]
		ret 0x10
	}

}

Voilà, maintenant c’est prêt, on lance le tout et on peut voir :

Syscall ZwQuerySystemInformation found !
Replacing code ...
TAG  0x7c91d910
 +0    L4  e9 07 b6 9c ec       jmp    $0x10001000
 +5    L3  b8 ad 00 00 00       mov    $0x000000ad -> %eax
 +10   L3  ba 00 03 fe 7f       mov    $0x7ffe0300 -> %edx
 +15   L3  ff 12                call   (%edx) %esp -> %esp (%esp)
END 0x7c91d910

Hop un hook d’api native sans modification de la mémoire !

Je m’arrête là pour aujourd’hui, j’espère vous avoir convaincu d’utiliser plus souvent cette techno pour le profiling et l’instrumentation de vos programmes. Personnellement j’aurais aimé l’utilisé dans un cadre plus offensif car pouvoir modifier du code sans toucher à la mémoire du binaire est toujours cool. Mais les requis de bases et la diminution (légère) de perfs ne m’ont pas emballé. En tout cas je n’irais pas foutre DynamoRio dans un rootkit. Qui plus est la lib ne fonctionne pas en kernel-land, ouais je sais, je me plains.

Encore une mauvaise nouvelle. J’ai testé un binaire packé avec UPX sur DynamoRio et il s’est lamentablement planté, dommage il aurait été intéressant d’appliquer ce genre de techno sur de tels binaires.

Bref, une techno super intéressante qui mérite qu’on s’y attarde mais qui demande à devenir plus mature dans le futur. Il faut bien sur reconnaitre le travail impressionnant réalisés par ses auteurs mais il manque encore des petites choses pour la rentre plus accessible.

Vous trouvez les codes+binaires ici :
http://ivanlef0u.fr/repo/DynamoRio.rar

Sinon, je vous conseil de tester Bochs, un émulateur de machine x86, pour ma part j’ai réussi à y installer un Windows XP et ça ne marche pas trop mal.

Aller aussi jouer avec NativeClient de Google, ca à l’air de bien défoncer.

Peter Ferrie lache un paper intéressant sur différentes méthodes d’anti-unpacking.

A signaler le retour de Arteam.

Le meilleur pour la fin : L33chma, je kiff ton boul !

Et surtout n’oubliez pas :
BE MAD !

4 comments janvier 6th, 2009


Calendar

janvier 2009
L Ma Me J V S D
« nov   fév »
 1234
567891011
12131415161718
19202122232425
262728293031  

Posts by Month

Posts by Category