Version txt

                             ==Phrack Inc.==

               Volume 0x0c, Issue 0x41, Phile #0x08 of 0x0f


|=---------------------=[ Mistifying the debugger, ]=--------------------=|
|=---------------------=[   ultimate stealthness   ]=--------------------=|
|=-----------------------------------------------------------------------=|
|=------------------------=[ halfdead@phear.org ]=-----------------------=|
|=--------------=[ Traduit par aryliin pour arsouyes.org ]=--------------=|


--[ Introduction 

Ces dernières années, il y a eu foule de techniques et de méthodes pour
permettre a quelqu'un de cacher sa présence dans un système piraté.
Plusieurs d'entre elles s'intéressent à falsifier directement la table
des appels, d'autres modifient le gestionnaire d'interruption, pendant que 
d'autres opèrent au niveau de la couche du VFS. Mais toutes modifient le
système d'exploitation sous-jacent de manière visible, les rendant faciles
à détecter.

Dans cet article, je vais présenter une technique capable d'atteindre une
furtivité ultime en matière de rootkits noyaux, en utilisant une 
caractéristique fréquente des architectures x86, le système de débuggage.
Bien que ce système marche sur toutes les plateformes compatibles IA-32, la
technique suivante sera détaillée pour un système Linux et je vous montrerai
comment intercepter le flot normal d'exécution sans toucher les cibles 
classiques. En fait, cette technique peut être si bonne que personne ne 
s'apercevra jamais de notre présence.

Quand nous parlons de "debugger" dans cet article, nous voulons en fait dire
le mécanisme de débug IA-32, qui est accessible uniquement du ring zéro.
Les debugger niveau utilisateur n'utilisent pas ce mécanisme, seuls quelques
debugger noyau le font.

--[ Le debugger

        "L'architecture IA-32 fournit d'amples commodités de
        débug à utiliser pour debugger un code, surveiller une 
        exécution d'un code et les performances d'un processeur.
        Ces commodités sont précieuses pour debugger des logiciels 
        applicatifs, des logiciels systèmes et des systèmes
        d'exploitation multitâches.

Dans le but de rendre la vie plus simple aux développeurs, Intel a introduit 
un mécanisme qui cherche à gérer le processus de débuggage. Ce mécanisme est
géré par un groupe de registres spéciaux (appelés 'debugging registers', 
DR0..DR7) qui autorisent l'utilisateur à mettre des breakpoint hardware sur des
adresses mémoire. Dès qu'un flot d'exécution atteint une adresse marquée d'un
point d'arrêt, il donne le contrôle au système d'interruption débug (INT 1), 
qui appelle la fonction do_débug() (définie dans ../i386/kernel/traps.c) afin
de s'occuper de la situation qui a lancée l'exception.

Le support de débug est accessible a travers les registres de débug (DB0 à DB7)
et deux registres spécifiques (MSRs). Dans ce papier, nous ne nous 
intéresserons qu'aux registres de débug Ces registres contiennent les 
adresses mémoires et les ports d'entrée/sortie, appelés breakpoint. 
Les breakpoint sont des endroits d'un programme définis par l'utilisateur, 
une zone de stockage dans la mémoire, ou un port d'entrée/sortie où le 
programmeur ou le concepteur veux arrêter l'exécution d'un programme et 
examiner l'état du processeur en appelant un logiciel de débuggage

Une exception de débug (#DB) est générée quand un accès à la mémoire ou 
à une entrée/sortie à une de ces adresses est fait. Un breakpoint est 
spécifique à une certaine forme de mémoire ou d'accès I/O, comme une lecture
mémoire et/ou une écriture, ou une lecture I/O et/ou une écriture. Les 
registres de débug supportent à la fois les breakpoint sur les instructions
et sur les données. Les registres MSRs (qui ont été introduits dans 
l'architecture IA-32 par la famille de processeurs P6) gère les branchements, 
les interruptions, les exceptions et enregistrent les adresses des derniers
branchements, interruptions ou exceptions, et la dernière branche prise
avant une interruption ou une exception.


--[ Les registres de débug

Il y a 8 registres de débug supportés par les processeurs Intel, qui 
contrôlent les opérations de débug du processeur. Ces registres peuvent être 
écrits et lus en utilisant la forme 'move to' et 'move from' de l'instruction
MOV. Un registre de débug peut être la source ou la destination d'une de ces 
instructions. Les registres de débug sont des ressources privilégiées; une 
instruction MOV qui accède à ces registres peut le faire uniquement en
mode réel d'adressage, SMM ou mode protégé avec un niveau de privilège (CPL)
de 0. Une tentative de lecture ou d''écriture dans ces registres à partir 
de n'importe quel autre niveau privilégié génère une exception de protection.

La fonction première des registres de débug est de mettre et gérer de 1 à 4
breakpoints, numérotés de 0 à 3. Le mécanisme de débug nous permet de gérer les
breakpoints a partir de deux registres spéciaux, DR6 et DR7, que je décrirai
plus tard. Pour chaque breakpoint, les informations suivantes peuvent être 
spécifiées et/ou détectées avec les registres de débug :

    - L'adresse à laquelle le breakpoint est survenu
    - La longueur de l'emplacement du breakpoint. (1, 2 ou 4 octets)
    - L'opération qui sera effectuée à l'adresse par l'exception générée
    - Si le breakpoint est actif
    - Si la condition du breakpoint était présente lorsque l'exception
      a été générée.

-------[ Les registres d'adresses de débug

Chacun des registres d'adresse (DR0-DR3) contient une adresse 32-bit d'un 
breakpoint. La comparaison entre l'adresse et le breakpoint à lieu avant que
la traduction en adresse physique ait lieu.

-------[ Les registres de débug DR4 et DR5

Les registres de débug DR4 et DR5 sont réservés pour les extensions de 
débuggage (le flag DE dans le registre de contrôle CR4 est 
actif), et des tentative de référencement de ces registres soulèvent une
exception de type 'invalid-opcode'. Quand le flag DE n'est pas activé, ces
registres sont des alias de DR6 et DR7.

------[ Registre de statut débug (DR6)

Ce registre spécial est utilisé pour rapporter les conditions de débuggage
si elles existent au moment ou la dernière exception est survenue. Les flags
dans ce registre montrent les informations suivantes : 

    - B0..B3 (bits 0..3) indiquent que la condition de breakpoint a été 
      détectée. Ces flags sont mis si la condition décrite pour chaque
      breakpoint par les drapeaux LENn, R/Wn du registre de contrôle DR7 est
      mis. Ils sont mis même si le breakpoint n'est pas activé par les 
      drapeaux Ln et Gn du registre DR7.

    - BD (bit 13) (accès détecté aux registres de débug) indique que la 
      prochaine instruction dans le flot d'instruction va accéder à un des
      registres de débug (DR0..DR7). Ce drapeau est activé quand le drapeau
      de détection générale (GD) du registre de contrôle DR7 est activé.

    - BS (bit 14) (simple pas) indique (s'il est mis) que l'exception de 
      débug a été attrapée par le mode d'exécution pas à pas.

    - BT (bit 15) (changement de tâche) indique (si mis) que l'exception 
      résutle d'un changement de tâche où le drapeau 'débug trap' dans le 
      TSS de la tache cible est actif.

Le processeur ne vide jamais le contenu du registre DR6.

------[ Le registre de contrôle (DR7)

Le registre de contrôle débug (DR7) active ou désactive les breakpoints et 
met des conditions. Ses drapeaux et champs contrôlent les points suivant :

    - L0..L3 (bits 0, 2, 4, 6) (activation breakpoint local) active (quand mis)
      la condition associée à un breakpoint pour la tâche courante. Quand une
      condition de breakpoint est détectée, une exception de débug est générée. 
      Le processeur vide automatiquement ces drapeaux à chaque changement de 
      tâche, pour éviter une condition de breakpoint sur une nouvelle tâche.

    - G0..G3 (bits 1, 3, 5, 7) (activation breakpoint globale) active (quand 
      mis) la condition de breakpoint associée au breakpoint pour toutes les 
      tâches. Quand une condition de breakpoint est détectée, et que son flag
      Gn est mis, une exception de débug est générée. Le processeur ne vide pas
      les flags sur un changement de tâche, autorisant ainsi le breakpoint
      pour toutes les tâches

    - LE et GE (bits 8 et 9) (activation exacte globale et locale) permet au 
      processeur de détecter l'instruction exacte qui a causée la condition
      de breakpoint sur une donnée. N'est pas supportée par la famille de 
      processeurs P6. 

    - GD (bit 13) (activation détection générale) active (si mis) la 
      protection des registres de débug, qui fait qu'un exception de débug
      est lancée avant chaque instruction MOC accédant à un registre de débug
      Quand une telle condition est détectée, le flag BD dans le registre de 
      statut DR6 est mis juste avant de générer l'exception.

    - R/W0..R/W3 (bits 16, 17, 20, 21, 24, 25, 28, et 29 ) (lecture/écriture)
      spécifient la condition de breakpoint pour le breakpoint donné.
      Pour plus d'information, lire le manuel Intel.

    - LEN0..LEN3 (bits 18, 19, 22, 23, 26, 27, 30, et 31) (longueur)

--[ La magie

Ok, donc nous avons appris à peu près tout à propos du mécanisme de débuggage
IA-32. Où sont les bonus que l'on vous a promis ?? Maintenant nous savons 
quelques choses importantes : Nous pouvons mettre un breakpoint à une adresse
mémoire et dès que l'exécution atteint cette adresse, elle est redirigée vers
le gestionnaire de débug (INT 1). Umm, que ce passe-t-l si nous remplaçons le
gestionnaire de débug ou une des fonctions sous-jacentes par une des nôtres.
Comme nous pouvons le voir dans entry.S

    ENTRY(débug)
        pushl $0
        pushl $ SYMBOL_NAME(do_débug)
        jmp error_code

le gestionnaire de débug actuel est une fonction C, do_débug(), définie dans
traps.c. Oui, ok, je pense que nous sommes capables de patcher le gestionnaire
INT 1 et appeler do_débug() de nous même OU nous pouvons venir avec notre 
propre do_débug et attendre d'être appelé par le gestionnaire de débug, donc
nous sommes assurés que la table d'interruption reste intouché.
Mais que va gérer notre gestionnaire ? Bien sur, nous avons besoin de vérifier 
quelques paramètres et de donner le contrôle à l'actuel do_débug(). Mais 
quels paramètres allons nous surveiller ? Continuez à lire ...

------[ détourner sys_call_table[]

Maintenant, vous devriez avoir une idée de la manière de détourner la table
des appels systèmes en utilisant les méchanismes de débugging d'Intel pour
redirriger le flot d'exécution. En placant des breakpoints matériels sur
les lectures/écritures/exécutions d'adresses mémoires cibles. Ça peut être
soit l'adresse du gestionnaire d'interruption INT 80, soit l'adresse de la
table des appels systèmes, ça n'a pas grande importance puisque l'effet est
le même, au final. Donc, à chaque fois que le système d'exploitation va
faire un appel système, il va remonter dans notre gestionnaire. Nous avons
ici deux options : A ) Détourner le gestionnaire d'interruption INT 80
directement dans la table d'interruption (IDT) ou B) Détourner l'adresse
actuelle de sys_call_table[] en mémoire. Chacun convient pour notre but,
donc nous allons nous focaliser sur A. La fonction suivante va renvoyer
l'adresse du gestionnaire d'interruption INT 80.

	get_idt_entry:
        	sidt    idtr
	        movl    idtr+2, %ebx
        	leal    (%ebx, %eax, 8), %ebx
	        movw    (%ebx), %cx
	        roll    $16, %ecx
	        movw    0x6(%ebx), %cx
	        roll    $16, %ecx
	        movl    %ecx, %eax
        	ret

Une fois que l'on connaît cette adresse, nous pouvons mettre un breakpoint 
de la manière suivante: 
	
	set_bpm:
        	movl    $0x80, %eax
	        call    get_idt_entry
        	movl    %eax, %dr0
	        xorl    %eax, %eax
        	orl     $0x2080, %eax
	        movl    %eax, %dr7
        	ret

Comme vous pouvez le voir, la fonction set_bpm() va charger dans DR0 l'adresse
où es située INT 80 et, aussi, mettre les flags correspondants dans DR7, 
y compris le bit magique GD, qui nous autorise à surveiller POURQUOI et QUI
accède aux registres de débug Ce bit est vraiment important pour nous car
il "permet de générer une exception de débug avant chaque instruction MOV 
accédant a un registre de débug". Oh, vous voulez dire ... ? Yeah, si QUELQU'UN
est en train d'essayer de lire/écrire dans un registre de débug, notre 
gestionnaire prend le contrôle AVANT que l'instruction soit effectuée. Donc, 
nous savons si quelqu'un, un debugger ou quelque outils du diable, regarde les
registres de débug, avant même qu'il le sache. Cela nous donne le temps de 
couvrir nos traces : nous pouvons tout défaire et attendre un moment que le 
danger soit passé, nous pouvons simplement sauter l'instruction affectant les
registres de débug, etc. La meilleure chose à faire est de montrer un système
avec des variables de débug propres et de, après un moment, tout redétourner
pour convenir à nos besoins. La meilleure approche est de prendre un 
émulateur de code, d'analyser l'instruction accédant au registres de débug, et 
la connaissant, décider de l'action a faire : nettoyer les registres de débug,
et les restaurer plus tard, ou simplement augmenter le compteur d'instruction
pour que l'instruction soit tout simplement ignorée. Cela étant, la discussion
reste ouverte.

------[ Le gestionnaire

Maintenant, on s'est débrouillé pour rediriger le flot d'exécution sans 
patcher quoique ce soit dans la table des appels systèmes ou dans le 
gestionnaire d'interruption INT 80 . Mais qu'a besoin de gérer notre 
gestionnaire ?
D'abord, dans sa forme la plus simple, notre gestionnaire doit vérifier les 
valeurs du registre %eax, car il contient à ce moment la valeur désirée de 
l'appel système, et grâce à ça nous pouvons donner a l'OS notre appel système
détourné. Voilà a quoi devrait ressembler notre gestionnaire simple :

asmlinkage void new_do_débug(struct pt_regs * regs, long error_code) 
{

  	unsigned long condition;
	unsigned long mask = 0x2008;

  
 	__asm__ __volatile__("movl %%db6,%0" : "=r" (condition));

  	if (condition & BD_FLAG) { /* someone is r/w the registers */
            condition &= ~BD_FLAG;
            __asm__ __volatile__ ("movl %0, %%db6" : : "r" (condition));
	    regs->eip += 3;
	    __asm__ __volatile__ ("movl %0, %%db7" : : "r" (mask));
        }
	
	if (condition & DR_TRAP0) {
            if (regs->eax == __NR_time) 
	        sys_call_table[__NR_time] = hacked_time;
	    
            if (regs->eflags & VM_MASK) {
                (*old_do_débug)(regs,error_code);
                __asm__ __volatile__ ("movl %0, %%db7" : : "r" (mask));
            }

            condition &= ~DR_TRAP0;
            __asm__ __volatile__ ("movl %0, %%db6" : : "r" (condition));
            __asm__ __volatile__ ("movl %0, %%db7" : : "r" (mask));
            regs->eflags |= X86_EFLAGS_RF;
        } 
	else 
	{
            (*old_do_débug)(regs, error_code);
            __asm__ __volatile__ ("movl %0, %%db7" : : "r" (mask));
	}

        return;
}

Que faisons nous donc ? D'abord récupérons les valeurs du registre de
statut (DR6) et tachons de comprendre ce qu'a récupéré notre gestionnaire.
Si notre exception est le résultat d'un breakpoint que nous avons placé,
nous allons comparer la valeur du registre %eax avec la valeur de l'appel
système que nous avons décidé de détourner, qui dans notre cas est
sys_time(). Dans l'exemple fournit, à cause du manque d'espace et de temps,
nous avons directement convertit the sys_call_table[], mais ça ne doit pas
vous inquiéter, hacked_time() modifie sys_call_table[] à nouveau afin de le
remettre comme avant dès qu'il est exécuté :

asmlinkage long hacked_time(int *tloc)
{
 	sys_call_table[__NR_time] = original_time;
	printk("<1>WE changed it!!\n");
 	return original_time(tloc);
}

Bien sur, il y a d'autres moyens de faire, sans toucher à la table des appels 
systèmes mais nous devons remarque que la première chose que fait hacked_time()
est de rechanger la valeur dans sys_call_table[], ce qui signifie que le 
changement actuel prend moins d'une microseconde et n'est pas un problème.

Une meilleure méthode serait d'analyser les paramètres de l'appel système, 
en se basant sur le numéro de l'appel système, qui se trouve à ce moment dans
le registre %eax. Nous pouvons donner les paramètres piratés simplement en 
remplissant le registre correspondant. Cette méthode va créer une table
"virtuelle" des appels systèmes, et donc nous ne toucherons pas du tout a la
table des appels systèmes. 

Donc nous avons vu comment mettre un breakpoint sur une adresse mémoire, 
comment l'activer; nous avons également appris que nous pouvons détourner le 
flot normal d'exécution sans toucher au gestionnaire d'interruption INT 80
ni à la table d'appels systèmes en elle même. Oui, vous pouvez dire que c'est
une belle technique, un peu magique. Pourtant, nous modifions le gestionnaire
d'interruption INT 1, ou du moins, nous patchons la fonction do_débug(), donc
nous ne sommes pas si furtifs que ça. Mais continuez à lire... 

---[ Bandeau (pour les yeux)

Nous avons appris pleins de belles choses jusqu'à maintenant, nous avons pris
le contrôle d'un système et personne ne peut détecter directement une 
modification du noyau. Nous couvrons nos traces grâces aux bits GD/BD donc, si
quelqu'un regarde les registres de débug, nous ignorons tout simplement leur
curiosité (regs -> eip +=3). Mais que ce passe-t-il si quelqu'un veut vérifier 
tout l'IDT dans son intégralité ? Ou si un debugger ou un outils similaire a 
besoin de mettre son propre gestionnaire dans INT 1 ? Sommes nous perdus ?
Bien sur que ça y ressemble..

Mais attendez.. DR6 et DR7 viennent à notre rescousse une fois de plus. Voilà
ce dont nous avons besoin :

    - mettez votre gestionnaire sur INT 1
    - mettez un breakpoint sur l'adresse de INT 80
    - mettez un second breakpoint pour regarder l'adresse de notre gestionnaire

Attendez ! Ça ne peut pas être si simple ! Si ça l'es ! Comme ça, nous 
n'affectons presque pas le noyau, pour des yeux voyeurs. Dans un gestionnaire
idéal, l'émulateur de code vérifierais le type de l'instruction qui essaie 
d'accéder aux registres de débug, si le breakpoint est sur INT 80 ou sur INT 1
et agirais en conséquence. Nous avons déjà expliqué ce que nous devions faire 
pour détourner INT 80, parlons maintenant de INT 1. En plaçant un second 
breakpoint sur INT 1 ou sur la fonction do_débug(), nous sommes surs à priori 
de savoir quand quelqu'un essaie d'accéder au seul endroit de la mémoire noyau
que nous ayons modifié. La meilleure chose à faire est de remettre cette 
adresse à sa valeur initiale. Comme ça, si un outils diabolique essaie de 
vérifier notre présence dans l'IDT (je ne pense pas qu'il y ai beaucoup d'outils
faisant ça, mais c'est simplement parce qu'aucun whitehat n'a pensé que c'était
nécessaire), nous lui laisserons voir la valeur inchangée. C'est un mode de 
couverture profonde "deep cover" mode. Mais avons nous perdu le contrôle du 
noyau ? Pas vraiment, nous en avons encore le contrôle, nous pouvons
"réinstaller" notre rootkit après quelques nanosecondes, et donc ils nous 
manquerons à chaque fois qu'ils nous chercherons. C'est comme leur bander les
yeux. Cette technique est également utile quand on a à faire à un debugger 
(ou un outils similaire) qui essaie de mettre son propre hook [NDT: il n'y a pas
vraiment de traduction dans ce cas pour hook, c'est dans le jargon] dans le 
gestionnaire d'interruption INT 1. Pensez y : Nous détectons une tentative, et 
remettons tout à la normale, ils mettent leur hook, nous détournons leur hook 
comme un détournement normal de INT 1 et dès que nous detectons leur présence, 
par exemple en vérifiant la présence du gestionnaire, nous leur laissons voir
ce qu'ils désirent. C'est comme faire une chaîne de hook, ou quelque chose du 
genre. Quand je l'ai découvert, j'ai été étonné. Quand j'ai réalisé que ça 
marchais vraiment, j'ai été émerveillé. C'est la furtivité ultime, le saint
graal du pirate!

---[ Mots de la fin

Cette technique a été activement utilisée par l'underground depuis plus de 8 
ans. La beauté de la chose : c'est en fait une fonctionnalité basique IA-32.
Ils ne peuvent pas la contrecarrer sans retirer entièrement le mécanisme de
débug J'ai décidé de la rendre publique dans phrack à travers un papier 
"scientifique" *g* mais ce n'était pas mon choix, la technique ayant été 
dévoilée il y a quelques temps. J'ai de gros doute sur le fait que la 
personne qui l'a dévoilée connaisse exactement ce que cet outil est 
vraiment capable de faire, et ce qu'il fait en ce moment, donc j'ai décidé
de l'aider lui, et tous les autres pirates du monde qui veulent apprendre et
se perfectionner. Comme vous l'avez vu, c'est une technique extrêmement 
puissante, permettant à quelqu'un une furtivité totale sur un système. 
Le fait que ce soit une caractéristique fondamentale des processeurs signifie
qu'elle peut être utilisée sur TOUS les systèmes tournant sous IA-32, et donc,
qu'il n'y a aucun moyen de la contrecarrer, bien que ce ne soit plus un 
0day ;(

---[ Clins d'oeil

halvar, twiz, reverser, sd et le reste de the digitalnerds

---[ Note du traducteur :

NDT-1 : La phrase originale de la vo est la suivante :
        "Now you should have an idea how to hijack the syscall table
         making use onunnt on read/write/execution on targetted
         address in memory."
	Après une explication de l'auteur, il doit s'agir d'une erreur dans la
	version hébergée sur le site de phrack. "onunnt" n'existe bien pas.
	La phrase française tient compte de ses commentaires.

le 30/05/2008 par halfdead [trad TboWan]