Advances in x86 hardware debugging mechanisms

juillet 22nd, 2009 by admin

Des mois se sont écoulés depuis que halfdead nous a presenté son très bon article sur l’utilisation des debug registers pour gagner encore plus en furtivité dans la conception de kernel rootkits (et des mois se sont également écoulés depuis que j’ai écris mon dernier article ici mais ça c’est est une autre affaire dans laquelle seul certains élus ont la reponse au « pourquoi »; je vous laisse alors mener votre enquête ;) ). Depuis, certains en ont profité pour release leur implementation, reinventant de par ce fait la roue car ayant « oublié » que d’autres l’avait déjà fait 2 ans auparavant (sans parler de rustock.C et d’autres virus datant de plus de 10ans!). Bref c’était assez phun de voir tout le remue menage qu’il a faillit y avoir autour (au passage, Joanna Rutkowska a une manière vraiment particulière de poser des questions :D ) si bien que j’apporte aussi ma pierre au bouzin, histoire d’avoir encore plus de matière à lullz.
En lisant donc de haut en bas et même en diagonale toutes les sources d’infos que j’avais, il apparaissait qu’il êtait soit disant impossible d’acceder aux debug registers depuis l’userland. Or je vais montrer que cela n’est pas tout à fait vrai. Certes on ne peut pas les utiliser à « pleine capacité » mais on peut quand même et ce contre certaines attentes, et avec un bon cerveau on peut deja aller loin avec ce qu’on a. Ensuite on va redescendre en kernel land pour montrer les limites de la technique utilisée par halfdead pour tirer parti de cette feature du x86 et pourquoi pas proposer un trick poussant le vice un peu plus loin ? ;)

Ok interressons nous donc d’abord à ces moyens permettant d’acceder à ces joyaux des processeurs x86. Chaque descripteur de processus dans linux contient une structure thread_struct qui contient l’état des registres spécifiques au processeur (notament les precieux debug registers):

// include/linux/sched.h#L1115
struct task_struct {
        ...
/* CPU-specific state of this task */
        struct thread_struct thread;
        ...
}
------------------
// arch/x86/include/asm/processor.h#L391
struct thread_struct {
        ...
     /* Hardware debugging registers: */
        unsigned long           debugreg0;
        unsigned long           debugreg1;
        unsigned long           debugreg2;
        unsigned long           debugreg3;
        unsigned long           debugreg6;
        unsigned long           debugreg7;
        ...
}

Dans l’implementation du scheduler, la fonction __switch_to() qui est la fonction au coeur du changement de contexte matériel et de la commutation de processus montre comment sont gérés ces debug registers lors d’une commutation de processus:

// arch/x86/kernel/process_32.c#L564
__notrace_funcgraph struct task_struct *
__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
         ...
         if (unlikely(task_thread_info(prev_p)->flags & _TIF_WORK_CTXSW_PREV ||
                      task_thread_info(next_p)->flags & _TIF_WORK_CTXSW_NEXT))
                 __switch_to_xtra(prev_p, next_p, tss);
         ...
}

Et puis

// arch/x86/kernel/process.c#L181
void __switch_to_xtra(struct task_struct *prev_p, struct task_struct *next_p,
                      struct tss_struct *tss)
{
        struct thread_struct *prev, *next;
 
        prev = &prev_p->thread;
        next = &next_p->thread;
 
        if (test_tsk_thread_flag(next_p, TIF_DS_AREA_MSR) ||
            test_tsk_thread_flag(prev_p, TIF_DS_AREA_MSR))
                ds_switch_to(prev_p, next_p);
        else if (next->debugctlmsr != prev->debugctlmsr)
                update_debugctlmsr(next->debugctlmsr);
 
        if (test_tsk_thread_flag(next_p, TIF_DEBUG)) {
                set_debugreg(next->debugreg0, 0);
                set_debugreg(next->debugreg1, 1);
                set_debugreg(next->debugreg2, 2);
                set_debugreg(next->debugreg3, 3);
                /* no 4 and 5 */
                set_debugreg(next->debugreg6, 6);
                set_debugreg(next->debugreg7, 7);
        }
 ...

Ainsi, ce n’est que lorsque le processus « entrant » (next) est marqué du flag TIF_DEBUG que les debug registers sont réellement mis à jour (les membres de la thread_struct sont copiés dans les registres matérielles). Il serait donc interessant de savoir comment modifier depuis l’userland les valeurs des debugregX de la structure thread_struct afin de pouvoir poser des hardware breakpoints ET aussi (et surtout) de pouvoir gerer l’exeception qui sera levée.
Dans Understanding the Linux Kernel, on apprend (à propos de la fonction __switch_to() ) que:
« …Charge six des registres de debogage dr0, …, dr7 avec le contenu du tableau next_p->thread.debugreg. Cela est effectué uniquement si next_p utilisait ces registres lorsqu’il a été suspendu (donc si le champ next_p->thread.debugreg[7] est different de 0). Ces registres n’ont pas besoin d’être sauvegardés car le tableau prev_p->thread.debugreg est modifié uniquement si un debogueur veut surveiller prev« .
Or nous voulons pouvoir modifier la thread_struct de notre process pour qu’au moment où il sera re-scheduler, les debug registers soit mis à jour avec les valeurs que nous avons definis. On se penche alors du coté des capacités de debogage logiciel offert par le système.
Afin de faciliter le debogage, il existe une zone « émulée » dans la representation d’un processus appelée la zone user (user area). Je dis émulée parce qu’il me semble que cette zone n’existe pas vraiment dans le sens où il n’y a pas un pointeur spécifique representant cette zone dans le descripteur de processus (comme pour la stack), mais la structure user qui la represente permet d’effectuer des opérations sur certains registres particuliers n’etant pas accessibles depuis la structure user_regs_struct habituellement utilisée. La structure representant la user area a une gueule semblable (modulo les commentaires):

// arch/x86/include/asm/user_32.h#L100
struct user{
  struct user_regs_struct regs; 
  int u_fpvalid;                
  struct user_i387_struct i387; 
  unsigned long int u_tsize;    
  unsigned long int u_dsize;    
  unsigned long int u_ssize;    
  unsigned long start_code;     
  unsigned long start_stack;    
 
  long int signal;              
  int reserved;                 
  unsigned long u_ar0;          
 
  struct user_i387_struct *u_fpstate;   
  unsigned long magic;          
  char u_comm[32];              
  int u_debugreg[8];
};

L’API de debug standart de linux (ptrace) permet d’acceder à cette zone grace à deux requêtes, PTRACE_PEEKUSR et PTRACE_POKEUSR, permettant respectivement de lire et d’écrire un mot dans la user area du processus auquel on s’attache.

// arch/x86/kernel/ptrace.c#L854
long arch_ptrace(struct task_struct *child, long request, long addr, long data)
{
	int ret;
	unsigned long __user *datap = (unsigned long __user *)data;
 
	switch (request) {
	/* read the word at location addr in the USER area. */
	case PTRACE_PEEKUSR: {
		unsigned long tmp;
 
		ret = -EIO;
		if ((addr & (sizeof(data) - 1)) || addr < 0 ||
		    addr >= sizeof(struct user))
			break;
 
		tmp = 0;  /* Default return condition */
		if (addr < sizeof(struct user_regs_struct))
			tmp = getreg(child, addr);
		else if (addr >= offsetof(struct user, u_debugreg[0]) &&
			 addr <= offsetof(struct user, u_debugreg[7])) {
			addr -= offsetof(struct user, u_debugreg[0]);
			tmp = ptrace_get_debugreg(child, addr / sizeof(data));
		}
		ret = put_user(tmp, datap);
		break;
	}
 
	case PTRACE_POKEUSR: /* write the word at location addr in the USER area */
		ret = -EIO;
		if ((addr & (sizeof(data) - 1)) || addr < 0 ||
		    addr >= sizeof(struct user))
			break;
 
		if (addr < sizeof(struct user_regs_struct))
			ret = putreg(child, addr, data);
		else if (addr >= offsetof(struct user, u_debugreg[0]) &&
			 addr <= offsetof(struct user, u_debugreg[7])) {
			addr -= offsetof(struct user, u_debugreg[0]);
			ret = ptrace_set_debugreg(child,
						  addr / sizeof(data), data);
		}
		break;
		...

A partir d’ici on sait déjà comment utiliser les debug registers depuis l’userland. Mais on ignore encore jusqu’où on est limité. On se penche donc du coté de ptrace_set_debugreg qui est la fonction chargée veritablement d’écrire dans la structure thread_info nos watchpoints:

// arch/x86/kernel/ptrace.c#L486
static int ptrace_set_debugreg(struct task_struct *child,
			       int n, unsigned long data)
{
	int i;
 
	if (unlikely(n == 4 || n == 5))
		return -EIO;
 
	if (n < 4 && unlikely(data >= debugreg_addr_limit(child)))
		return -EIO;
 
	switch (n) {
	case 0:		child->thread.debugreg0 = data; break;
	case 1:		child->thread.debugreg1 = data; break;
	case 2:		child->thread.debugreg2 = data; break;
	case 3:		child->thread.debugreg3 = data; break;
 
	case 6:
		if ((data & ~0xffffffffUL) != 0)
			return -EIO;
		child->thread.debugreg6 = data;
		break;
 
	case 7:
#ifdef CONFIG_X86_32
#define	DR7_MASK	0x5f54
#else
#define	DR7_MASK	0x5554
#endif
		data &= ~DR_CONTROL_RESERVED;
		for (i = 0; i < 4; i++)
			if ((DR7_MASK >> ((data >> (16 + 4*i)) & 0xf)) & 1)
				return -EIO;
		child->thread.debugreg7 = data;
		if (data)
			set_tsk_thread_flag(child, TIF_DEBUG);
		else
			clear_tsk_thread_flag(child, TIF_DEBUG);
		break;
	}
 
	return 0;
}
 
------------------------------------------------------------------------------
 
static unsigned long debugreg_addr_limit(struct task_struct *task)
{
	return TASK_SIZE - 3;
}

Et c’est là qu’on crie w0000000t: le bouzin est uber-checké de partout :(
L’adresse à laquelle on veut placer un watchpoint doit imperativement être plus basse que TASK_SIZE – 3 (par ailleurs on note cependant que le flag TIF_DEBUG est bien set après la modification de la struct thread_info, en adéquation avec ce qui est observé plus haut). En gros donc on ne peut pas placer de breakpoints dans des pages situées en kernel land. De plus le check des conditions ne permet pas de breaker sur les I/O (cf les man intel Debug Control Register (DR7): R/Wi fields). Autant dire que c’est pas avec ça qu’on va pwn teh worldz (i.e syscall_table …)
Mais avant revenons donc à comment gerer l’exeception levée après qu’une condition definie pour un watchpoint soit verifiée. La solution la plus simple (la seule?) que j’ai à ma connaissance est de catch le signal SIGTRAP qui est send au processus après que do_debug est finit de handler la trap.

dotraplinkage void __kprobes do_debug(struct pt_regs *regs, long error_code)
{
         struct task_struct *tsk = current;
         unsigned long condition;
         int si_code;
 
         get_debugreg(condition, 6);
 
         /*
          * The processor cleared BTF, so don't mark that we need it set.
          */
         clear_tsk_thread_flag(tsk, TIF_DEBUGCTLMSR);
         tsk->thread.debugctlmsr = 0;
 
         if (notify_die(DIE_DEBUG, "debug", regs, condition, error_code,
                                                 SIGTRAP) == NOTIFY_STOP)
                 return;
 
         /* It's safe to allow irq's after DR6 has been saved */
         preempt_conditional_sti(regs);
 
         /* Mask out spurious debug traps due to lazy DR7 setting */
         if (condition & (DR_TRAP0|DR_TRAP1|DR_TRAP2|DR_TRAP3)) {
                 if (!tsk->thread.debugreg7)
                         goto clear_dr7;
         }
 
 #ifdef CONFIG_X86_32
         if (regs->flags & X86_VM_MASK)
                 goto debug_vm86;
 #endif
 
         /* Save debug status register where ptrace can see it */
         tsk->thread.debugreg6 = condition;
 
         /*
          * Single-stepping through TF: make sure we ignore any events in
          * kernel space (but re-enable TF when returning to user mode).
          */
         if (condition & DR_STEP) {
                 if (!user_mode(regs))
                         goto clear_TF_reenable;
         }
 
         si_code = get_si_code(condition);
         /* Ok, finally something we can handle */
         send_sigtrap(tsk, regs, error_code, si_code); /**** YES!!! HERE ****/
 
         /*
          * Disable additional traps. They'll be re-enabled when
          * the signal is delivered.
          */
 clear_dr7:
         set_debugreg(0, 7);
         preempt_conditional_cli(regs);
         return;
 
 #ifdef CONFIG_X86_32
 debug_vm86:
         /* reenable preemption: handle_vm86_trap() might sleep */
         dec_preempt_count();
         handle_vm86_trap((struct kernel_vm86_regs *) regs, error_code, 1);
         conditional_cli(regs);
         return;
 #endif
 
 clear_TF_reenable:
         set_tsk_thread_flag(tsk, TIF_SINGLESTEP);
         regs->flags &= ~X86_EFLAGS_TF;
         preempt_conditional_cli(regs);
         return;
 
}

Et voila! :)

Revenons donc à la technique d’halfdead. Son algorithme reposait sur le fait de remplacer l’adresse de la fonction (do_debug) appelée par le handler de l’interruption int 1 par l’adresse de son propre code. De ce fait à chaque fois que cette exception est levée, c’est son propre do_debug qui est appelé à l’instar de l’original. Ensuite il lui suffit de placer un watchpoint sur l’adresse du handler de l’interruption int 0×80 dans l’idt pour qu’à chaque fois que l’os voudra performer un syscall, la fonction do_debug sous son controle soit appelée avant que la moindre instruction ne soit executée. De ce fait dans cette fonction, il peut rediriger le syscall vers la fonction de son choix. Tout ceci laisse en plus l’idt et la syscall_table intactes (hormis le fait d’avoir changé UNIQUEMENT l’adresse de do_debug dans le handler de int 1). Pour pousser le machiavelisme à son comble et donc empecher les petits curieux comme toi qui veulent checker l’idt, il va placer un second watchpoint à l’adresse de sa propre fonction do_debug pour que si jamais il venait en tete à quelqu’un de checker l’integrité de l’idt il puisse replacer l’adresse de la fonction original avant que tu n’ai le temps de faire quoi que ce soit, d’attendre juste quelques temps et de remodifier cette adresse une fois de plus. Si c’est pas méchant ca?! Au dessus de tout ça, il set le bit 13 (general detect) du registre DR7 à chaque fois pour savoir si quelqu’un essaie d’acceder aux debug registers et de se fait, l’envoyer balader ou n’importe quoi d’autre (par exemple emuler le fait d’avoir posé un bp alors qu’en fait non). Avec ce mecanisme c’est vraiment difficile de s’imaginer qu’il puisse y avoir un moyen de le defaire vu qu’il a pensé à tout.
Maintenant quand je parlais des limites de cette technique c’est à prendre au sens large (non non je t’ai pas menti :p). Premierement, si on se place dans le cas d’un systeme sain, le moyen le plus simple de se premunir est d’empecher purement et simplement l’utilisation de ces registres en les tenant tous tout le temps occupés et en placant le bit GD à 1 pour empecher toute utilisation posterieur. En effet dans les commentaires (qui sont, ma foi, parfois aussi passionnant que les papers) de l’article sur phrack, halfdead lui meme affirme que c’est le seul moyen mais que c’est une methode de facho terroriste trop extremiste et qu’il valait tout aussi mieux eteindre la becane. Mais si on y reflechi mieux, je ne connais perso aucun programme faisant usage de ces registres (vu leur faible nombre), alors quitte à empecher l’utilisation de l’inutilisé, pourquoi pas? J’ai certes vu des extraits de code from les sources de gdb où il en est question mais je n’ai jamais vraiment entendu parler du fait que gdb les utilisait (si vous avez des infos, soyez gentils et sortez moi de mon eternel ignorance).
Ensuite si on se place dans le cas d’un système déjà infecté, tout dependra de comment est géré le cas par le rootkit où quelqu’un veut utiliser les debug registers. Si le rootkit ignore simplement la requete alors c’est simple:
- on remet tous les DR à 0 et on place 4 watchpoints quelque par dans notre propre code.
- ensuite on essaie d’acceder à ces 4 adresses et si on n’a pas 4 exceptions de levée c’est qu’il y’a au moins un des registres dont le fonctionnement a été emulé et donc rootkit il y’a :p
D’autres ont aussi proposés des tricks comme par exemple mesurer le temps d’excecution du code chargé de handler l’interruption mais aucune ne me paraissent aussi logique que les 2 ci-dessus.

Pour finir, il se trouve qu’avec le temps, je sois devenu un paresseux, mais alors tout ce qu’il y’a de pire au point de ne pas avoir pris le temps de coder un petit truc illustrant ce que je dis. Je me protège en disant que c’est de la faute à (on ne dit pas « de la faute de ». le concerné se reconnaitra :p) 0vercl0k qui ma poussé à sortir cet article au plus vite parce que je foutais rien selon lui. Honte à lui n’est ce pas? :D
Neamoins vous pouvez trouver ci dessous un lien vers un p0c montrant comment utiliser les debug registers depuis l’userland.
http://blogs.sun.com/nike/entry/memory_debugger_for_linux
En realité je fait vraiment quelque chose de très important au point qu’il me prend tout mon temps libre (mis à part le social hein :p). Je vais peut etre etre de moins en moins present ici mais vous entendrez parler très bientot de moi ou plutot de nous dans un projet qui je l’espere va apporter sa pierre à la nouvelle génération representant la scène fr, sans forcement savoir que c’est la meme personne. Les plus attentifs de ceux avec qui je traine sauront surement de quoi il s’agit.

Greetz à pouik pour avoir LE lien qu’il faut au moment où il le faut, à halfdead pour avoir pris le temps de m’expliquer et pour son paper croustillant (if u ever read this, u are definitely the faggot :) ), à ivan parce qu’il le vaut bien :)

Reverse Ur m1nd!

décembre 11th, 2008 by admin

Hi all,

N’ayant rien à faire de mon (pas si) precieux temps, je me suis amusé à reverser le level 20 de notre bon petit site de challenge français (******contest quoi!). Un jeune crackme aux petites saveurs d’obfuscation, et de chiffrement… Read the rest of this entry »

The art of exploitation: NULL (kernel) pointer dereference

novembre 28th, 2008 by admin

Hi all,

Je partage aujourd’hui une nouvelle technique de la mort qui tue dont j’ai eu vent il n’y a pas si longtemps, permettant de s’octroyer un shell root sur une machine en local (ou plus connu sous le doux nom de « privilege escalation » par les habitués).  Pour reprendre une belle phrase d’Ivanlef0u à ma façon, on va réutiliser un code s’exécutant dans le noyau linux contre lui même (plus précisement c’est un module, mais comme les modules s’exécutent en kernel land on va pas faire tout un plat pour un petit abus de langage). Fun n’est ce pas? En plus la phrase roxx, donc fallait que je la mette. Attachez vos ceintures, je vous amène faire un petit plongeon au coeur de Linux. Read the rest of this entry »

Small Ext3fs integrity checker to perform hidden files detection.

novembre 3rd, 2008 by admin

Hi tapz,

Je release aujourd’hui un article que j’ai écris pour le microblog nibbles. C’est une petite contribution de ma part pour ce projet. (greetz à j0rn et à Ivanlef0u)
Je divaguais comme d’habitude dans les confins du net lorsque je fus pris soudain d’une diarrhée envie de savoir comment détecter un rootkit (efficacement ou pas), car oui soyons clair, on est pas supposé être toujours les attaquants. Cet alors dans ce but que je publie aujourd’hui ce petit article. Read the rest of this entry »

HZV Magazine #01 is out

novembre 2nd, 2008 by admin

J’ai un peu hésité avant de faire suivre mais je release tout de même ce petit billet pour faire part de la sortie du premier magazine (new version) de chez les confrères d’Hackerzvoice disponible , et dans lequel j’ai laché il y’a quelques mois un article concernant l’exploitation web basé sur une faille de type mt_srand(). Heh oui dude, je n’ai pas toujours été un geekotapz à mentalité el33tiste voulant faire à la *linux style* les kata d’Ivanlef0u et d’0vercl0k.

Avec du recul, je remarque que l’article en soi n’était pas aussi mauvais que je le pensais il y’a encore quelques temps, vu qu’il traite de l’analyse de l’exploitation d’une vulnéralibité des applications web, qui était peu documentée contrairement aux pas-si-fameux-que-ça injections SQL ou XSS, mais qui étaient très bien connu de l’underground.

Au final, mon impression sur l’ensemble du magazine est qu’il pas mal avec des articles comme celui d’0vercl0k sur l’exploitation de stacks overflows sous windows mais avec en prime des techniques sur comment bypasser le flag GS de Microsoft Visual C++ Studio, ainsi que la protection par Data Execution Prevention. Il explique en plus l’exploitation par réécriture de SEH. En gros si vous êtes un windowzien boutonneux comme lui, vous ne pourrez que vous en lecher les babines.

A noter également le petit article de Celelibi « Codage de données » expliquant comment les differents types de données sont stockés dans les plus bas niveaux de la machine.

D’autres articles sont par contre un peu plus useless comme « La toile à nue face à au renard ». Je vais me passer de commentaires dénigrants, car c’est tout de même du travail de personnes dont on parle mais je n’y pas trouvé mon compte pour ma part. Ce n’est pas rien mais espérons voir mieux la prochaine fois.

Anyway, lisez-le et jugez par vous même.

Congratz à tout leur staff pour la release.

A bientôt

Le format ELF: approche du point de vue d’un infecteur.

septembre 25th, 2008 by admin

Pour ce premier billet je vais faire une petite présentation du format ELF (Executable and Linking Format). C’est principalement le format des fichiers binaires où est enrigistré le code compilé sur les systèmes de type UNIX (dont Linux, les *BSD, Solaris… font partie). Pour un infecteur, il est assez important d’avoir un certain nombre d’informations sur le format du fichier à infecter notamment la struture du fichier (ici un executable), donc les differentes sections et segments… afin de se multiplier correctement (je ne considère pas le cas des virii qui overwrite le fichier host). Read the rest of this entry »

(Re)Ouverture

septembre 25th, 2008 by admin

Bonjour,
C’est en ce modeste Dimanche de Septembre 2008 que j’inaugure mon petit coin du net à moi tout seul. L’idée me trottinait déjà depuis un bout de temps dans la tête mais ce n’est qu’aujourd’hui que je le lance.
Ce blog a pour simple but de faire part de l’avancée de mes recherches portant sur la virologie (en informatique) et bien sur l’étude des techniques visant à contrer/supprimer/éradiquer/pulvériser … (ok vous avez compris donc j’arrête ) ces petites bêtes que vous détestez tant mais qui sont fascinantes d’un point de vue technique. Mes recherches portent aussi entre autre sur les techniques d’intrusion et de maintient d’accès, principalement sous les plateformes de type unix (je me contenterais de linux) et windows, et les moyens de les « empêcher » (ou plus précisément comment les rendre moins accessibles car empêcher totalement toute intrusion est tout bonnement impossible). Que personne ne s’étonne si de temps en temps je publie aussi un billet portant uniquement sur ma minable vie.
Peut être qu’un jour je serais le plus grand distributeur de solutions anti-virales au monde, à la tête d’une colossale multinationale et croulant sous les billets (et les femmes bien sur). Il n’est pas interdit de rêver …
A bientôt

PS:  J’en profite pour signaler que j’ai finalement migré chez tuxfamily. Une équipe formidable m’a acceuilli et je tiens à les remercier de tout coeur.