<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Segmentation fault &#187; Reverse Engineering</title>
	<atom:link href="https://www.segmentationfault.fr/tag/reversing/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.segmentationfault.fr</link>
	<description>Projets d’un consultant en sécurité informatique</description>
	<lastBuildDate>Fri, 15 Feb 2019 08:02:10 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.4.2</generator>
		<item>
		<title>Bypassing SEHOP on Windows 7</title>
		<link>https://www.segmentationfault.fr/securite-informatique/bypassing-sehop-on-windows-7/</link>
		<comments>https://www.segmentationfault.fr/securite-informatique/bypassing-sehop-on-windows-7/#comments</comments>
		<pubDate>Mon, 21 Dec 2009 12:59:28 +0000</pubDate>
		<dc:creator>Emilien Girault</dc:creator>
				<category><![CDATA[Reverse Engineering]]></category>
		<category><![CDATA[Sécurité informatique]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[hacking]]></category>
		<category><![CDATA[seven]]></category>

		<guid isPermaLink="false">http://www.segmentationfault.fr/?p=740</guid>
		<description><![CDATA[La protection SEHOP introduite dans Windows Vista et 2008 permet de protéger les applications contre les exploitations de buffer-overflows classiques. Celles-ci consistent en général à écraser non seulement une adresse de retour, mais aussi la structure SEH gérant les exceptions provoquées par l&#8217;application, dans le but de rediriger le flux d&#8217;exécution vers un shellcode. La [...]]]></description>
			<content:encoded><![CDATA[<p>La protection SEHOP <a href="http://blogs.technet.com/srd/archive/2009/02/02/preventing-the-exploitation-of-seh-overwrites-with-sehop.aspx">introduite</a> dans Windows Vista et 2008 permet de protéger les applications contre les exploitations de buffer-overflows classiques. Celles-ci consistent en général à écraser non seulement une adresse de retour, mais aussi la structure SEH gérant les exceptions provoquées par l&rsquo;application, dans le but de rediriger le flux d&rsquo;exécution vers un shellcode. La protection SEHOP empêche ce type d&rsquo;exploitation en parcourant au préalable la chaîne des structures SEH et en s&rsquo;assurant qu&rsquo;elle soit valide. Jusqu&rsquo;à maintenant, elle était considérée par beaucoup comme inviolable. Microsoft ont d&rsquo;ailleurs décidé de l&rsquo;activer par défaut dans Windows 2008, ainsi que dans Windows Vista et 7 sous forme de fix.<span id="more-740"></span></p>
<p>Or, deux collègues de <a href="http://www.sysdream.com/">Sysdream</a>, Stéfan Leberre (<a href="http://www.ghostsinthestack.org">Heurs</a>) et Damien Cauquil (<a href="http://www.virtualabs.fr">Virtualabs</a>), viennent de trouver une méthode permettant de la contourner sous certaines conditions. Leur article ainsi que leur Proof Of Concept est disponible sur le site de Sysdream :</p>
<ul>
<li><a href="http://www.sysdream.com/articles/sehop_en.pdf">L&rsquo;article Bypassing SEHOP</a></li>
<li><a href="http://www.sysdream.com/SEHOP.zip">Proof Of Concept fonctionnant sous Windows 7</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://www.segmentationfault.fr/securite-informatique/bypassing-sehop-on-windows-7/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RDTSC hooking sous Linux : théorie et pratique</title>
		<link>https://www.segmentationfault.fr/securite-informatique/rdtsc-hooking-linux/</link>
		<comments>https://www.segmentationfault.fr/securite-informatique/rdtsc-hooking-linux/#comments</comments>
		<pubDate>Tue, 14 Jul 2009 12:30:35 +0000</pubDate>
		<dc:creator>Emilien Girault</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Reverse Engineering]]></category>
		<category><![CDATA[Sécurité informatique]]></category>
		<category><![CDATA[hacking]]></category>
		<category><![CDATA[ring 0]]></category>
		<category><![CDATA[x86]]></category>

		<guid isPermaLink="false">http://www.segmentationfault.fr/?p=688</guid>
		<description><![CDATA[L&#8217;architecture x86 possède des subtilités parfois méconnues de beaucoup de développeurs. En effet, il existe une instruction assez spéciale, RDTSC, qui renvoie le nombre de cycles d&#8217;horloge depuis le démarrage du processeur. En 2007, un chercheur d&#8217;IBM présente au Black Hat une technique de hook basée sur cette instruction. En effet, il se trouve qu&#8217;il [...]]]></description>
			<content:encoded><![CDATA[<p>L&rsquo;architecture x86 possède des subtilités parfois méconnues de beaucoup de développeurs. En effet, il existe une instruction assez spéciale, RDTSC, qui renvoie le nombre de cycles d&rsquo;horloge depuis le démarrage du processeur. En 2007, un chercheur d&rsquo;IBM <a href="https://www.blackhat.com/presentations/bh-usa-07/Yason/Whitepaper/bh-usa-07-yason-WP.pdf">présente </a>au Black Hat une technique de hook basée sur cette instruction. En effet, il se trouve qu&rsquo;il existe un flag dans le registre de contrôle CR4 permettant de désactiver cette instruction en ring 3, et de déclencher une exception #GP (int 13) lors de son appel. Via un hook de l&rsquo;IDT par un driver codé maison, il devient donc possible de détourner les appels ring 3 à RDTSC, de filtrer les résultats et imaginer toutes sortes de choses. D&rsquo;autant plus que RDTSC est couramment utilisée dans des application ayant trait à la sécurité, comme les méthodes d&rsquo;anti-debugging ou de génération de nombres aléatoires&#8230;</p>
<p><span id="more-688"></span></p>
<p>Pour cet article, on se propose d&rsquo;écrire un driver qui effectuera ce hook et qui détournera RDTSC afin de rendre les valeurs 11223344 et 55667788 respectivement dans EAX et EDX lorsqu&rsquo;on l&rsquo;appelle. Je présente en premier lieu la théorie nécessaire pour l&rsquo;attaque, puis décris comment l&rsquo;implémenter sous Linux. Enfin, je détaille une difficulté majeure à laquelle on peut faire face sur les distributions récentes telles qu&rsquo;ArchLinux : le flag TIF_NOTSC.</p>
<h3>L&rsquo;instruction RDTSC et le flag TSD</h3>
<p>RDTSC signifie &laquo;&nbsp;ReaD TimeStamp Counter&nbsp;&raquo;, autrement dit elle permet de lire le compteur de temps du processeur, incrémenté à chaque cycle d&rsquo;horloge. Ce compteur n&rsquo;est autre que le MSR IA32_TIME_STAMP_COUNTER (cf <a href="http://www.intel.com/products/processor/manuals/">manuel 3B d&rsquo;Intel</a>, section 18.11). Celui-ci fait 64 bits et est retourné dans EDX et EAX lors de l&rsquo;appel à RDTSC. Les applications classiques s&rsquo;en servent généralement pour :</p>
<ul>
<li>Effectuer des mesures de performance (benchmark) sans passer par les fonctions du noyau</li>
<li>Générer des nombres pseudo-aléatoires, à cause du caractère à priori non prévisible de ce compteur (surtout des bits de poids faible)</li>
<li>Détecter des débogueur en mesurant des deltas entre deux instructions fixes ; si un débogueur est présent et qu&rsquo;un breakpoint a été posé (ou que le mode step-by-step a été utilisé), le temps écoulé sera beaucoup plus long donc il est facile à l&rsquo;application de quitter.</li>
</ul>
<p>Cependant, ce n&rsquo;est pas exactement comme cela qu&rsquo;est décrit l&rsquo;instruction RDTSC dans le <a href="http://www.intel.com/products/processor/manuals/">manuel 2B d&rsquo;Intel</a>. En effet, on peut y lire le pseudo-code suivant :</p>
<pre>IF (CR4.TSD = 0) or (CPL = 0) or (CR0.PE = 0)
THEN EDX:EAX ? TimeStampCounter;
ELSE (* CR4.TSD = 1 and (CPL = 1, 2, or 3) and CR0.PE = 1 *)
#GP(0);
FI;</pre>
<p>On y apprend que le registre CR4 possède un flag TSD qui, s&rsquo;il est activé, provoquerait une exception lorsque RDTSC est appelé dans un ring supérieur à 0 (mode protégé). Cette exception est la General Protection Fault, notée #GP et définie à l&rsquo;index 13 dans l&rsquo;IDT (table des interruptions). L&rsquo;OS traite cette exception par une routine du noyau qui n&rsquo;est généralement pas prévue pour gérer ce cas, donc on aura par défaut droit à un crash du programme ayant appelé RDTSC. Sous Linux, cela se traduit par l&rsquo;envoi d&rsquo;un signal SIGSEGV au processus, causant une segmentation fault.</p>
<h3>IDT hooking</h3>
<p>Pour hooker RDTSC, il faut donc dans un premier temps mettre à 1 le flag TSD (bit 2) de CR4 pour déclencher une #GP. Mais ce que nous voulons, c&rsquo;est appeler notre fonction et non celle du noyau lors de l&rsquo;exception. Il va donc falloir patcher l&rsquo;IDT en remplaçant l&rsquo;adresse du handler 13 par le notre ; autrement dit, faire du IDT hooking.</p>
<p>L&rsquo;IDT recense des descripteurs explicités à la section 5.11 du manuel 3A d&rsquo;Intel. Les descripteurs suivant plus ou moins le même format :</p>
<div id="attachment_690" class="wp-caption aligncenter" style="width: 352px"><a href="http://www.segmentationfault.fr/wp-content/uploads/2009/07/int_desc.PNG"><img class="size-full wp-image-690" title="Interrupt Descriptor" src="http://www.segmentationfault.fr/wp-content/uploads/2009/07/int_desc.PNG" alt="Descripteur d'interruption" width="342" height="124" /></a><p class="wp-caption-text">Descripteur d&#39;interruption</p></div>
<p>Comme d&rsquo;habitude dans la doc Intel, le schéma se lit de bas en haut et de droite à gauche (little endian, quand tu nous tiens&#8230;). A la mode des autres descripteurs propres à l&rsquo;architecture x86, on constate que le champ Offset est découpé en deux parties : poids forts et poids faibles. C&rsquo;est ce champ qui pointe vers le handler à exécuter lors de l&rsquo;exception. Il suffit de remplacer sa valeur par l&rsquo;adresse d&rsquo;une de nos fonctions, et nous pourrons alors détourner le flux d&rsquo;exécution lors d&rsquo;un appel ring 3 à RDTSC.</p>
<h3>Trouver l&rsquo;IDT</h3>
<p>Pour pouvoir faire un hook de l&rsquo;IDT, il faut d&rsquo;abord savoir la trouver. En fait, il est nécessaire de préciser que pour les processeurs multi-coeur, il n&rsquo;y a pas une seule IDT mais plusieurs :  une par cœur. Il est donc en théorie nécessaire de hooker toutes les IDT pour éviter les problèmes. Pour connaître l&rsquo;IDT référencée par un cœur, il suffit d&rsquo;utiliser l&rsquo;instruction SIDT sur ce cœur. Cette instruction est accessible en ring 3 ; <a href="http://nibbles.tuxfamily.org/?p=372">voici un code</a> qui l&rsquo;illustre. Cependant, si vous utilisez Linux dans une machine virtuelle telle que VirtualBox, il se peut que vous rencontriez des problèmes en fonction de vos options de virtualisation. En effet, l&rsquo;instruction SIDT n&rsquo;est pas toujours bien émulée par l&rsquo;hyperviseur et il se peut que la valeur qu&rsquo;elle retourne soit erronée. Préférez-donc la solution suivante si vous tenez à faire vos tests dans une VM.</p>
<p>Même si un procceseur peut avoir plusieurs IDT, Linux n&rsquo;en utilise qu&rsquo;une car chaque cœur référence la même. Celle-ci est définie dans le noyau par le symbole <code>idt_table</code>. Pour connaître son adresse, tentez :</p>
<pre>grep idt_table /proc/kallsyms</pre>
<p>Le premier champ retourné est l&rsquo;addresse de l&rsquo;IDT. Si cela ne vous renvoie rien, il vous faudra à la place utiliser le fichier /boot/Symbol.map, généré à la compilation du noyau. Il se peut que son nom soit quelque peu différent ; par exemple sous Ubuntu il suit le format /boot/System.map-$(uname -r) alors que sous ArchLinux il s&rsquo;appelle /boot/System.map26.</p>
<h3>Conception du handler</h3>
<p>Une bonne conception du nouveau handler d&rsquo;interruption est cruciale pour éviter de rendre instable tout le système. En effet, #GP est utilisée non seulement pour RDTSC mais aussi à chaque fois qu&rsquo;un check de privilèges échoue (pour une bonne ou une mauvaise raison) dans l&rsquo;OS, autrement dit un sacré paquet de fois&#8230; Autant dire qu&rsquo;il est préférable de laisser l&rsquo;OS gérer ces cas là tout seul.</p>
<p>Pour cela, il va falloir filtrer dans un premier temps les #GP dues à RDTSC et celles dues à une autre instruction. Détecter l&rsquo;instruction fautive est facile vu que l&rsquo;EIP a été empilé ; il suffit de le regarder, d&rsquo;examiner ce qu&rsquo;il pointe et de comparer cette valeur à l&rsquo;opcode de RDTSC : 0F 31, soit 0x310F en mot de 16 bits little endian. Si cela ne correspond pas, on saute sur le handler de base de l&rsquo;OS pour ne pas tout crasher.</p>
<p>Ce n&rsquo;est pas tout : les programmes ring 3 de l&rsquo;OS aussi utilisent RDTSC. Si nous leur rendons des valeurs comme 0&#215;11223344, ils risquent d&rsquo;avoir un comportement plutôt imprévisible, surtout s&rsquo;ils s&rsquo;en servent comme base de temps. J&rsquo;ai d&rsquo;ailleurs testé sous Linux ; Cron a segfaulté instantanément et la machine est devenue inutilisable en quelques secondes.</p>
<p>Bref, il faut se débrouiller pour rendre la bonne valeur à ces programmes. La solution est d&rsquo;émuler RDTSC dans le driver, et de transmettre les résultats dans EAX et EDX au ring 3. Mais comment savoir quand retourner les bonnes et valeurs et les fakes ? La solution la plus simple qui m&rsquo;est venue à l&rsquo;esprit est d&rsquo;utiliser le PIDs du processus courant, en supposant que l&rsquo;on connaisse le PID à hooker. Pour transmettre au driver le PID du processus en question, on peut utiliser des IOCTLs, justement prévues pour la communication ring 3 &#8211; ring 0. Une fois que l&rsquo;on a le PID, il suffit de consulter le PID courant et on peut savoir si on doit émuler RDTSC ou forger les valeurs.</p>
<h3>Récupération du PID courant</h3>
<p>Nous avons quasiment tout ce qu&rsquo;il faut pour implémenter cette attaque sous Linux. La seule chose qu&rsquo;il nous manque, c&rsquo;est un moyen dé récupérer le PID du processus courant lorsque l&rsquo;on est dans un handler d&rsquo;interruption. Après lecture en diagonale du chapitre 7 d&rsquo;<a href="http://books.google.fr/books?id=h0lltXyJ8aIC&amp;dq=understanding+linux+kernel&amp;printsec=frontcover&amp;source=bn&amp;hl=fr&amp;ei=RopbSsO8A4z0nQOdwfXdCQ&amp;sa=X&amp;oi=book_result&amp;ct=result&amp;resnum=4">Understanding The Linux Kernel 3rd edition</a>, on constate qu&rsquo;il existe une macro nommée <code>current</code> qui permet de récupérer un pointeur vers le descripteur de processus courant. Après avoir testé cette macro, je me suis rendu compte qu&rsquo;elle ne marche en fait pas dans le contexte d&rsquo;un handler d&rsquo;interruption. Il faut utiliser à la place la fonction <code>current_thread_info()</code> qui marche à tous les coups. A partir de là, récupérer le PID est très simple, via l&rsquo;expression suivante : <code>current_thread_info()-&gt;task-&gt;pid</code>.</p>
<h3>Implémentation 1</h3>
<p>Nous pouvons maintenant implémenter l&rsquo;attaque. Je l&rsquo;ai réalisé sans problèmes particulier sur une Ubuntu 9.04 avec un noyau 2.6.28, sur processeur AMD dualcore. Les sources sont disponibles plus bas ; voici les points principaux.</p>
<pre>//Typedefs
typedef unsigned char u_int8;
typedef unsigned short u_int16;
typedef unsigned int u_int32;
typedef unsigned long long int u_int64;

/**
 * An IDT entry. Cf Intel SDM 3A
 */
typedef struct {
 u_int16 low_offset;
 u_int16 selector;
 u_int8 unused_lo;
 u_int8 segment_type:4;
 u_int8 system_segment_flag:1;
 u_int8 DPL:2;
 u_int8 P:1;
 u_int16 hi_offset;
} __attribute__((packed)) IDTENTRY_ST, *P_IDTENTRY_ST;</pre>
<p>Dans un premier temps, on déclare la structure d&rsquo;un descripteur d&rsquo;interruption. On fera particulièrement attention à bien spécifier <code>__attribute__((packed))</code> pour spécifier au compilateur de ne pas faire de padding entre les champs. La fonction effectuant le hook est ci-après :</p>
<pre>//Interrupt handlers
u_int32 old_int_handler, new_int_handler2;

void HookOneIDT (P_IDTENTRY_ST _p_IDT, u_int32 _interrupt_number,
                 u_int32* _old_address, u_int32 _new_address)
{
 asm("cli\n\t");

 *_old_address =  ((_p_IDT[_interrupt_number].hi_offset &lt;&lt; 16)
                | (_p_IDT[_interrupt_number].low_offset));
 _p_IDT[_interrupt_number].hi_offset = (_new_address &gt;&gt; 16) &amp; 0xFFFF;
 _p_IDT[_interrupt_number].low_offset = (_new_address &amp; 0xFFFF);

 asm("sti\n\t");
}</pre>
<p>Rien de particulier ici, à part une désactivation temporaires des interruptions. D&rsquo;ailleurs, pour être plus rigoureux, il aurait fallu les désactiver sur tous les cœurs, mais comme cette fonction sera appelée avec <code>interrupt_number = 13</code>, qui n&rsquo;est de toutes façon pas masquable, il n&rsquo;y a pas de risque.</p>
<p>Le nouveau handler d&rsquo;interruption est codé à part dans un fichier assembleur. Il s&rsquo;agit en fait d&rsquo;un squelette qui sauvegarde le contexte et appelle une fonction C, pour des raisons de commodité :</p>
<pre>.globl interrupt_handler

//The interrupt handler.
//This function must be naked. Since it's not possible with gcc on x86 platforms, we put it in a separate asm file.
interrupt_handler: 

 //Save registers
 pusha
 pushf

 //Call our hook function and  the parameter
 //Since convention call of my_func_handler is fastcall, parameter has to be in %ecx
 mov %esp, %ecx
 call my_func_handler

 //Check the return value
 cmp $1, %eax

 //If 1, throw the exception away
 je  my_exit

 //Otherwise, restore registers
 popf
 popa

 //Jump to the original handler
 jmpl * old_int_handler

my_exit:

 //Restore registers
 popf
 popa

 //Pop interrupt error code
 add $4, %esp

 //Return from interrupt
 iret</pre>
<p>La fonction appelée, <code>my_func_handler</code>, doit déterminer la nature de l&rsquo;exception et la traiter si besoin en détournant RDTSC. On utilise son code de retour pour savoir si l&rsquo;on repasse la main au handler par défaut de Linux, ou si on se contente de retourner en userland.</p>
<pre>//Opcode for RDTSC : 0F 31 =&gt; 31 OF in little endian
#define RDTSC_OPCODE 0x310F

//Size of RDTSC instruction
#define RDTSC_SIZE   2

/**
 * Interrupt stack structures
 */
typedef struct
{
 u_int32 edi;
 u_int32 esi;
 u_int32 ebp;
 u_int32 esp;
 u_int32 ebx;
 u_int32 edx;
 u_int32 ecx;
 u_int32 eax;
} PUSHA_ST, *P_PUSHA_ST;

typedef struct
{
 u_int32 error_code;  // !! Check Intel Manuals to see if the error code is present or not
 u_int32 eip;
 u_int32 cs;
 u_int32 eflags;
 u_int32 esp;
 u_int32 sp;
} INT_STACK_HARD_ST, P_INT_STACK_HARD_ST;

typedef struct
{
 u_int32              eflags;

 PUSHA_ST             pusha_st;
 INT_STACK_HARD_ST    int_stack_hard_st;

} MY_INT_STACK_ST, *P_MY_INT_STACK_ST;

/**
 * Return current PID
 */
unsigned int GetCurrentPID (void)
{
 // !!! The 'current' macro doesn't work in interrupt context !
 // !!! We have to use current_thread_info()-&gt;task instead
 return current_thread_info()-&gt;task-&gt;pid;
}

/**
 * Function called by the interrupt handler.
 *  !! WARNING !! Don't call printk() inside, or the kernel will freeze !
 *
 * @param stack pointer to the stack
 * @return 0 if this is a normal #GP exception,
 * 1 if it is due to our RDTSC hook
 */
u_int32  __attribute__((__fastcall__))
         my_func_handler (P_MY_INT_STACK_ST stack)
{
 //nb_interrupts++;+
 asm volatile("lock incl nb_interrupts\n\t");

 //Detect if the instruction that triggered the exception is RDTSC
 if(* (u_int16*) stack-&gt;int_stack_hard_st.eip == (u_int16) RDTSC_OPCODE)
 {
 //Check who is executing RDTSC
 if(GetCurrentPID() == pid_to_hook)
 {
 //Change EAX and EDX with magic values
 stack-&gt;pusha_st.eax = 0x11223344;
 stack-&gt;pusha_st.edx = 0x55667788;
 }
 else
 {
 //Perform a normal call to RDTSC
 RDTSC_ST rdtsc;
 RDTSC(&amp;rdtsc);

 stack-&gt;pusha_st.eax = rdtsc.eax;
 stack-&gt;pusha_st.edx = rdtsc.edx;
 }

 //Increment EIP
 stack-&gt;int_stack_hard_st.eip += RDTSC_SIZE;

 return 1;
 }
 else
 {
 return 0;
 }

}</pre>
<p>Il y a plusieurs détails qui ont leur importance. D&rsquo;une part,on définit des structures correspondant à l&rsquo;état de la pile lors de l&rsquo;appel à cette fonction. Cela inclut les registres généraux pushés par PUSHA ainsi que les valeurs pushés automatiquement par le processeur. Il faut faire attention à bien inverser leur ordre relativement aux specifications d&rsquo;Intel, vu que la pile croît des addresses hautes vers les basses. On récupère l&rsquo;EIP empilé, on déréférence ce pointeur et on compare le mot de 16 bits avec l&rsquo;opcode de RDTSC renversé (vu qu&rsquo;il se trouve en mémoire, donc en little-endian). On émule RDTSC su besoin, et on n&rsquo;oublie pas d&rsquo;incrémenter EIP afin de sauter par dessus l&rsquo;instruction lors du retour. On notera que le debug de cette fonction n&rsquo;est pas trivial, car il est impossible d&rsquo;utiliser des fonctions comme <code>printk()</code> à l&rsquo;intérieur.</p>
<p>Voici désormais la partie relative aux IOCTLs. Je n&rsquo;ai pas détaillé cette partie précédemment car elle fait plutôt partie d&rsquo;un choix d&rsquo;implémentation.</p>
<pre>#include &lt;linux/ioctl.h&gt;

//The device name in /proc/devices
#define DEVICE_NAME        "rdtsc_exploit"

//The name of the device file in /dev
#define DEVICE_FILE_NAME   "/dev/rdtsc_exploit"

//IOCTL command codes
#define IOCTL_SET_PID    _IOWR(0, 0, unsigned int)

//Device major and minor numbers
static dev_t g_device_num;

//Count the number of hooked interrupts
extern volatile unsigned int nb_interrupts;

//The file_operation structure, to link the device
//to the appropriate handlers
static struct file_operations g_fops = {
 .owner   = THIS_MODULE,
 .ioctl   = my_ioctl,
};

//Char device structure
static struct cdev g_device;</pre>
<p>Sous Linux, pour pouvoir communiquer avec un module en utilisant des IOCTLs, il faut créer un périphérique virtuel en mode caractère (char device) et lui assigner un handler l&rsquo;ioctl. Ce device possèdera un numéro majeur dynamiquement alloué par le noyau. Pour le numéro mineur, nous choisissons simplement 0. Une fois ces ressources allouées, nous enregistrons le device ce qui a pour effet de le faire apparaître dans /proc/devices. Tout ce procédé est fort bien décrit aux chapitres 3 et 6 de <a href="http://lwn.net/Kernel/LDD3/">Linux Device Drivers, 3rd edition</a>, livre libre que je vous conseille vivement.</p>
<pre>/**
 * Create the device
 */
int create_device (void)
{
 //Allocate the device major and minor
 if(alloc_chrdev_region(&amp;g_device_num, 0, 1, DEVICE_NAME))
 {
 printk(KERN_INFO "ERROR: alloc_chrdev_region FAILED\n");
 return -1;
 }

 //Initialise the device
 cdev_init(&amp;g_device, &amp;g_fops);

 //Fill in some fields (optional)
 g_device.owner = THIS_MODULE;
 g_device.ops = &amp;g_fops;

 //Register the device into the kernel
 if(cdev_add(&amp;g_device, g_device_num, 1))
 {
 printk(KERN_INFO "ERROR: cdev_add FAILED\n");
 return -1;
 }

 printk(KERN_INFO "Device registrated successfully - name = %s, "
                  "major = %d, minor = %d\n", DEVICE_NAME,
                  MAJOR(g_device_num), MINOR(g_device_num));

 return 0;
}

/**
 * Delete the device
 */
void delete_device (void)
{
 //Unregister the device
 cdev_del(&amp;g_device);

 //Unregister the device number
 unregister_chrdev_region(g_device_num, 1);
}</pre>
<p>Ces deux fonctions réalisent la création et la suppression du device.</p>
<p>Pour manipuler le flag TSD de CR4, on cree les fonctions suivantes :</p>
<pre>//Flag of CR4 that disable RDTSC in userland
#define FLAG_DISABLE_USER_RDTSC 0x4

/**
 * Get CR4 value
 */
u_int32 GetCR4 (void)
{
 u_int32 res = 0;

 asm volatile (
 "push %%eax\t\n"
 "mov %%cr4, %%eax\t\n"
 "mov %%eax, %0\t\n"
 "pop %%eax\t\n"
 : "=m"(res));

 return res;
}

/**
 * Set CR4 value
 */
void SetCR4 (u_int32 _new_cr4)
{
 asm volatile(
 "push %%eax\t\n"
 "mov %0, %%eax\t\n"
 "mov %%eax, %%cr4\t\n"
 "pop %%eax\t\n"
 : : "m" (_new_cr4));
}

/**
 * Enable userland calls to RDTSC
 */
void EnableUserRDTSC (void)
{
 SetCR4(GetCR4() &amp; ~FLAG_DISABLE_USER_RDTSC);
}

/**
 * Disable userland calls to RDTSC
 */
void DisableUserRDTSC (void)
{
 SetCR4(GetCR4() | FLAG_DISABLE_USER_RDTSC);
}</pre>
<p>On notera au passage la syntaxe assez inhabituelle de l&rsquo;assembleur inline de GCC, notemment les doubles % nécessaires puisque l&rsquo;on utilise des références (%0), ainsi que les \n\t en fin de ligne. Et bien entendu, les arguments inversés par rapport à la syntaxe officielle d&rsquo;Intel.</p>
<p>Lors du chargement du driver, il suffira de hooker l&rsquo;IDT et de positionner le flag CR4.TSD. Cependant, cette dernière opération doit être faite sur tous les coeurs. On utilisera donc la macro <code>on_each_cpu()</code>.</p>
<pre>//Hook the General Protection Fault handler (0x0D)
#define INTERRUPT_VECTOR_TO_HOOK 0x0D

#include &lt;linux/module.h&gt;  /* Needed by all modules */
#include &lt;linux/kernel.h&gt;  /* Needed for KERN_ALERT */
#include &lt;linux/init.h&gt;     // Needed for the macros

#include "../include/defines.h"
#include "hook.h"
#include "device.h"

static int module_load(void)
{
 Hook();
 create_device();

 //Must return 0, otherwise the module is not loaded
 return 0;
}

static void module_unload(void)
{
 delete_device();
 UnHook();
}  

module_init(module_load);
module_exit(module_unload);

/**
 * Hook
 */
void Hook ()
{
 //Get the IDT address (all CPUS use the same)
 P_IDTENTRY_ST pIDT = GetIDTSoft();

 printk(KERN_INFO "interrupt_handler = %08x\n", (u_int32) interrupt_handler);

 //Hook interrupt handler
 HookOneIDT(pIDT, INTERRUPT_VECTOR_TO_HOOK,
            &amp;old_int_handler, (u_int32) interrupt_handler);

 //Hook RDTSC
 on_each_cpu(DisableUserRDTSC, 0, 0);
}

/**
 * Unhook
 */
void UnHook ()
{
 //Unhook RDTSC
 on_each_cpu(EnableUserRDTSC, 0, 0);

 //Unhook interrupt handler
 HookOneIDT(GetIDTSoft(), INTERRUPT_VECTOR_TO_HOOK,
            &amp;new_int_handler2, old_int_handler);
}</pre>
<p>Dans mon prototype, je récupère l&rsquo;adresse de l&rsquo;IDT en userland dans le Makefile&#8230;</p>
<pre>IDT_ADDRESS = "0x`grep idt_table /boot/System.map-2.6.28-11-generic
               | cut -d ' ' -f 1`"</pre>
<p>&#8230; que je passe en paramètre à GCC lors de la compilation avec le flag -D. Le module la récupère comme une constante pré-processeur :</p>
<pre>/**
 * Get a pointer to the IDT - the soft way.
 * Works perfectly in VMs, but we either have to hardcode the IDT offset,
 * or read it from userland ('grep idt_table /proc/kallsyms'
 * or 'grep idt_table /boot/System.map').
 */
P_IDTENTRY_ST GetIDTSoft (void)
{
 P_IDTENTRY_ST pIDT = 0;

 pIDT = (P_IDTENTRY_ST) IDT_ADDRESS;

 return pIDT;
}</pre>
<p>En userland, il faudra transmettre le PID à hooker au device, ce qui se fait par le code suivant :</p>
<pre>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;

#include &lt;fcntl.h&gt;
#include &lt;sys/ioctl.h&gt;

#include "defines.h"

int main(int ac, char **av)
{

 //int i;
 int fd;
 int pid_to_hook;

 if(ac != 2)
 {
 printf("Usage: set_pid pid\n");
 printf("Set the pid to hook.\n\n");
 exit(0);
 }

 pid_to_hook = atoi(av[1]);

 if(pid_to_hook &lt;= 0)
 {
 fprintf(stderr, "Error, pid must be &gt; 0.\n");
 exit(1);
 }

 //Open the device in order to communicate with the driver
 fd = open(DEVICE_FILE_NAME, O_RDONLY);

 if(fd == -1)
 {
 printf("Error: %s does not exist!\n", DEVICE_FILE_NAME);
 exit(1);
 }

 //Send IOCTLs to the driver to set the pid do hook
 if(ioctl(fd, IOCTL_SET_PID, pid_to_hook))
 {
 fprintf(stderr, "Error setting the pid.\n");
 }
 else
 {
 printf("pid set successfully.\n");
 }

 //Close the device
 close(fd);

}</pre>
<p>Enfin, la routine de traitement de l&rsquo;IOCTL qui sert à récupérer le PID dans le module est relativement simple :</p>
<pre>/**
 * IOCTL handler
 */
int my_ioctl (struct inode * _inode, struct file * _file,
              unsigned int _ioctl_num, unsigned long _ioctl_param)
{
 struct task_struct *task;

 switch(_ioctl_num)
 {
 //Set the PID
 case (IOCTL_SET_PID):

 pid_to_hook = (unsigned int) _ioctl_param;

 break;

 default:
 printk(KERN_INFO "rdtsc_exploit: ERROR: Unsupported ioctl code: "
                  "%08x.\n", _ioctl_num);
 }
 return 0;
}</pre>
<h3>Test</h3>
<p>Après avoir compilé le tout, on charge le module :</p>
<pre># insmod module/rootkit.ko
# grep rdtsc_exploit /proc/devices
250 rdtsc_exploit
# mknod /dev/rdtsc_exploit c 250 0</pre>
<p>On lance le programme exécutant RDTSC dans un shell à part :</p>
<pre>$ exe/rdtsc/rdtsc
Press &lt;Enter&gt; to call rdtsc. Press q to quit.

RDTSC result (edx : eax) = (0000126d : 8c1cc9a2)

RDTSC result (edx : eax) = (0000126d : a38e75be)</pre>
<p>Puis on envoie son PID au module avec :</p>
<pre># exe/set_pid/set_pid $(pidof rdtsc)
pid set successfully.</pre>
<p>Et on revient au terminal précédent, en constatant que le hook fonctionne bien :</p>
<pre>RDTSC result (edx : eax) = (55667788 : 11223344)

RDTSC result (edx : eax) = (55667788 : 11223344)

RDTSC result (edx : eax) = (55667788 : 11223344)</pre>
<p>On n&rsquo;oubliera pas de décharger le module avec :</p>
<pre># rmmod rootkit
# rm /dev/rdtsc_exploit</pre>
<h3>Problème avec ArchLinux</h3>
<p>En testant l&rsquo;implémentation précédente avec deux distributions ArchLinux de noyaux 2.6.29 et 2.6.30, j&rsquo;ai constaté qu&rsquo;ell ne marchait tout simplement pas. En faisant plusieurs tests, je constate que le handler de #GP est bien hooké, mais RDTSC ne l&rsquo;est pas du tout car le programme de test affiche toujours des valeurs normales. J&rsquo;affiche la valeur de CR4.TSD à plusieurs reprises, et je vois que de temps en temps, il repasse à 0, ce qui expliquerait pourquoi RDTSC n&rsquo;est pas détournée.</p>
<p>Après plusieurs recherches, je tombe sur <a href="http://blog.cr0.org/2009/05/time-stamp-counter-disabling-oddities.html">ce blog</a>, qui pointe du doigt quelques bizarreries du noyau Linux concernant justement le flag TSD. Apparemment, il serait possible de l&rsquo;activer ou non pour certains processus seulement. Il s&rsquo;agit du Thread Information Flag TIF_NOTSC définit dans le fichier arch/x86/include/asm/thread_info.h du noyau. Ce flag est plus ou moins l&rsquo;équivalent du flag TSD, mais dans le contexte de chaque processus. Il est possible de le définir avec l&rsquo;appel système <a href="http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html">prctl</a> en utilisant l&rsquo;option PR_SET_TSC. La valeur PR_TSC_ENABLE revient à positionner TSD = 0, tandis que PR_TSC_SIGSEGV est équivalent à TSD = 1.</p>
<p>Ces flags existent déjà dans les noyaux 2.6.28 d&rsquo;Ubuntu 9.04 ; je n&rsquo;ai pas encore bien saisi pourquoi ceuxi-ci sont effectivement appliqués sur ArchLinux. Le blog cité précédemment parle de l&rsquo;option de configuration CONFIG_SECCOMP du noyau, présente sur ArchLinux, mais visiblement désactivée ia le flag TIF_SECCOMP qui vaut 0 pour tous les processus. Je vais continuer mes recherches de ce côté&#8230; Si toutefois vous avez des explications, je suis preveur <img src='https://www.segmentationfault.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .</p>
<h3>Implémentation 2</h3>
<p>En attendant, il reste tout de même effectuer le hook de RDTSC. Il suffit de positionner le flag TIF_NOTSC du processus en question à PR_TSC_SIGSEGV. Cela peut se faire en appelant prctl, mais cette technique n&rsquo;est pas vraiment convenable car un hook se doit d&rsquo;être extérieur au processus. La technique consiste donc à émuler le fonctionnement de cet appel système au sein de notre module. Il nous suffit de parcourir la liste chainée des processus, d&rsquo;isoler celui qui a le bon PID, et à positionner son flag. Cela revient à modifier la fonction <code>my_ioctl()</code> comme ceci :</p>
<pre>/**
 * IOCTL handler
 */
int my_ioctl (struct inode * _inode, struct file * _file,
              unsigned int _ioctl_num, unsigned long _ioctl_param)
{
   struct task_struct *task;

   switch(_ioctl_num)
   {
      //Set the PID
      case (IOCTL_SET_PID):

         pid_to_hook = (unsigned int) _ioctl_param;
         printk(KERN_INFO "rdtsc_exploit: pid_to_hook = "
                          "%d.\n", pid_to_hook);

         for_each_process(task) {
            if(task-&gt;pid == pid_to_hook){
               test_and_set_ti_thread_flag(task_thread_info(task), TIF_NOTSC);
               printk("TIF_NOTSC set for process %d\n", task-&gt;pid);
            }
         }

      break;

      default:
         printk(KERN_INFO "rdtsc_exploit: ERROR: Unsupported ioctl code: "
                          "%08x.\n", _ioctl_num);
   }
   return 0;
}</pre>
<p>La macro <code>for_each_process()</code> définie dans linux/sched.h permet d&rsquo;itérer très simplement sur les threads du système. On utilise la fonction <code>test_and_set_ti_thread_flag()</code> afin de positionner le flag TIF_NOTSC du thread en question. On notera qu&rsquo;il n&rsquo;y a même plus besoin de modifier à la main CR4 à l&rsquo;initialisation.</p>
<h3>Sources</h3>
<p><a href="http://www.segmentationfault.fr/wp-content/uploads/2009/07/rdtsc_exploit.tar.gz">Téléchargez les sources</a></p>
<p>Les sources incluent l&rsquo;implémentation 2, sachant que celle-ci fonctionne aussi bien sur les deux distributions que j&rsquo;ai testées (Ubuntu et ArchLinux). Les lignes spécifiques à la 1ère implémentation sont commentées, donc vous pouvez toujours jouer avec et voir le résultat que vous obtenez.</p>
<p>Pour compiler, invoquez simplement <code>make</code> à la racine de rdtsc_exploit. Si jamais cela ne compile pas, éditez le fichier module/Makefile, et indiquez le bon chemin vers votre fichier /boot/System.map. Vérifiez également que le fichier module/handler.S a bien un S majuscule concernant son extension.</p>
<p>Les fichiers fournis sont organisés comme ceci :</p>
<ul>
<li>module/ contient les sources du module</li>
<li>exe/ contient deux sources d&rsquo;exécutables :
<ul>
<li>rdtsc : programme de test exécutant RDTSC à chaque appui sur une touche. Il contient aussi un fichier de test du noyau,  disable-tsc-test.c, que j&rsquo;ai jugé intéressant de garder pour des tests. A compiler séparément.</li>
<li>set_pid : programme prenant en paramètre le PID de rdtsc et l&rsquo;envoyant au module par ioctl</li>
</ul>
</li>
<li>scripts/ contient trois scripts permettant d&rsquo;automatiser le chargement du module et la création du device. load_hook.sh et unload_hook.sh appellent en réalité load.sh, capable de charger/décharger un module et créer/détruire son device.</li>
</ul>
<h3>Applications</h3>
<p>Pour terminer, voici quelques possibilités offertes par le hook de RDTSC :</p>
<ul>
<li><strong>Empoisonnement des générateurs de nombres pseudo-aléatoires</strong> : Certaines applications utilisent RDTSC comme source d&rsquo;aléa, pour générer des valeurs pseudo-aléatoires qui peuvent par exemple être utilisées pour la génération de clé de chiffrement. En forçant à RDTSC à renvoyer des valeurs bien précise, on peut injecter des valeurs bien précises dans l&rsquo;algorithme de génération et pouvoir prédire plus facilement son résultat.</li>
<li><strong>Anti-anti-debuging</strong> : Comme dit au premier paragraphe, une technique d&rsquo;anti-debug consiste à utiliser RDTSC pour estimer le temps passé entre deux instructions et le comparer à une valeur seuil. Une technique d&rsquo;anti-anti-debug peut donc être de hooker RDTSC et de retourner des valeurs plausibles à l&rsquo;application, en masquant le fait que celle-ci est en train de se faire déboguer. C&rsquo;est précisément ce que fait le plugin <a href="http://www.openrce.org/downloads/details/241/Olly_Advanced">Olly Advanced</a> d&rsquo;OllyDbg.</li>
<li><strong>Communication offusquée entre une application et un driver</strong> : Puisqu&rsquo;avec cette technique RDTSC est exécutée en ring 3 et provoque une exception #GP en ring 0, c&rsquo;est un moyen de donner la main à un driver afin qu&rsquo;il effectue des opérations &laquo;&nbsp;ni vu ni connu&nbsp;&raquo;, dans le sens ou il n&rsquo;y a aucun appel explicite vers fonction noyau dans l&rsquo;application ring 3.</li>
</ul>
<h3>Conclusion</h3>
<p>Cette technique n&rsquo;est pas nouvelle, mais encore assez peu connue (enfin sans doute pas des reversers :p). Cependant, elle peut se révéler très intéressantes dans de multiples occasions. Si je devais donner un conseil, ce serait d&rsquo;éviter de l&rsquo;utiliser en ring 3, pour deux raisons principales de sécurité :</p>
<ul>
<li>Il existe des générateurs aléatoires reconnus comme fiables, il est donc préférable de les utiliser plutôt que de se faire son propre algorithme.</li>
<li>L&rsquo;OS fournit généralement des appels systèmes permettant d&rsquo;appeler RDTSC en ring 0 et de retourner sa valeur (cf <code>NtQueryPerformanceCounter()</code> sous Windows). Comme l&rsquo;appel est en ring 0, la méthode de hook décrite précédemment ne marche plus.</li>
</ul>
<h3>Références</h3>
<ul>
<li><a href="https://www.blackhat.com/presentations/bh-usa-07/Yason/Whitepaper/bh-usa-07-yason-WP.pdf">The Art of Unpacking</a>, Marc Vincent Yason, BlackHat 2007</li>
<li><a href="http://www.intel.com/products/processor/manuals/">Intel Software Devloper&rsquo;s Manuals</a> 2B, 3A</li>
<li><a href="http://books.google.fr/books?id=h0lltXyJ8aIC&amp;dq=understanding+linux+kernel&amp;printsec=frontcover&amp;source=bn&amp;hl=fr&amp;ei=RopbSsO8A4z0nQOdwfXdCQ&amp;sa=X&amp;oi=book_result&amp;ct=result&amp;resnum=4">Understanding the Linux kernel, 3rd edition</a>, <span>Daniel Pierre Bovet, Marco Cesati, </span>O&rsquo;Reilly</li>
<li><a href="http://nibbles.tuxfamily.org/?p=372">Nibbles &#8211; SMP : IDT et GDT</a>, j0rn</li>
<li><a href="http://lwn.net/Kernel/LDD3/">Linux Device Drivers, 3rd edition</a>, Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman, O&rsquo;Reilly</li>
<li><a href="http://blog.cr0.org/2009/05/time-stamp-counter-disabling-oddities.html">CR0&prime;s blog : Time-stamp counter disabling oddities in the Linux kernel</a></li>
<li><a href="http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html">Man prctl</a></li>
</ul>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 11920px; width: 1px; height: 1px;">
<pre><strong><strong>PR_SET_TSC</strong></strong></pre>
</div>
]]></content:encoded>
			<wfw:commentRss>https://www.segmentationfault.fr/securite-informatique/rdtsc-hooking-linux/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Tunnez votre BSOD (Blue Screen Of Death)</title>
		<link>https://www.segmentationfault.fr/securite-informatique/tunnez-votre-bsod-blue-screen-of-death/</link>
		<comments>https://www.segmentationfault.fr/securite-informatique/tunnez-votre-bsod-blue-screen-of-death/#comments</comments>
		<pubDate>Thu, 30 Apr 2009 18:58:35 +0000</pubDate>
		<dc:creator>Emilien Girault</dc:creator>
				<category><![CDATA[Reverse Engineering]]></category>
		<category><![CDATA[Sécurité informatique]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[ring 0]]></category>

		<guid isPermaLink="false">http://www.segmentationfault.fr/?p=631</guid>
		<description><![CDATA[Tous les développeurs de drivers et les reversers kernel vous le diront : quand on touche au noyau Windows d&#8217;un peu trop près, on a le droit à un écran bleu de la mort, ou BSOD (Blue Screen Of Death). C&#8217;est justement parce que je me suis pris un tas de BSOD que j&#8217;ai commencé [...]]]></description>
			<content:encoded><![CDATA[<p>Tous les développeurs de drivers et les reversers kernel vous le diront : quand on touche au noyau Windows d&rsquo;un peu trop près, on a le droit à un écran bleu de la mort, ou BSOD (Blue Screen Of Death). C&rsquo;est justement parce que je me suis pris un tas de BSOD que j&rsquo;ai commencé à en avoir marre de voir des écrans bleus partout et à me poser des questions existentielles du genre : pourquoi l&rsquo;écran bleu est-il bleu ? Est-ce possible de changer cela ? Oui, et je vais montrer dans cet article comment faire en ne modifiant qu&rsquo;un octet en mémoire kernel. Matériel requis : une deuxième machine (une VM fait très bien l&rsquo;affaire), Windbg, IDA PRO (facultatif) et quelques neurones.<span id="more-631"></span></p>
<h3>Le pourquoi du BSOD</h3>
<p>Avant de mettre les mains dans le cambouis, il est intéressant de comprendre pourquoi Windows affiche un écran bleu. Cet écran maléfique est affiché lorsque quelque chose d&rsquo;assez sérieux s&rsquo;est produit au niveau du noyau. Cela peut être aussi bien matériel que logiciel (parfois les deux). Par exemple : un driver tente d&rsquo;accéder à une zone mémoire non existante ou protégée. Si vous essayez de faire cela avec un programme en en mode utilisateur, celui-ci crashera en affichant un message d&rsquo;erreur Windows (ou une <em>segmentation fault</em> sous Linux). En mode noyau, l&rsquo;équivalent est le BSOD sous Windows (<em>Kernel Panic</em> sous Linux) et vous n&rsquo;avez pas d&rsquo;autre choix que de rebooter votre bécane. Eh oui, on ne plaisante pas avec le kernel. Voici un exemple de driver qui provoquera un écran bleu à coup sûr :</p>
<pre>* (unsigned int *) 0 = 0;</pre>
<p>Ce code essaye d&rsquo;écrire 0 à l&rsquo;adresse mémoire virtuelle 0, qui est invalide. Par conséquent, le processeur déclenchera un défaut de page, ce qui provoque un écran bleu puisque son descripteur est invalide. Il y a plein d&rsquo;autres manières de provoquer un écran bleu, mais cela dépasse le cadre de cet article.</p>
<h3>Reversing de la routine du BSOD</h3>
<p>Le code de la routine affichant le BSOD se situe dans le kernel Windows, dans ntoskrnl.exe (ou ntkrnlpa.exe si vous avez la PAE). Après avoir débuggé une machine virtuelle et provoqué un écran bleu volontairement grâce au code donné ci-dessus, je m&rsquo;aperçois que la VM s&rsquo;est arrêtée dans la routine <code>KeBugCheckEx</code>. J&rsquo;ouvre ntoskrnl.exe avec IDA PRO et désassemble cette routine.</p>
<pre>public _KeBugCheckEx@20
_KeBugCheckEx@20 proc near

BugCheckCode= dword ptr  8
BugCheckParameter1= dword ptr  0Ch
BugCheckParameter2= dword ptr  10h
BugCheckParameter3= dword ptr  14h
BugCheckParameter4= dword ptr  18h

mov     edi, edi
push    ebp
mov     ebp, esp
push    0
push    [ebp+BugCheckParameter4]
push    [ebp+BugCheckParameter3]
push    [ebp+BugCheckParameter2]
push    [ebp+BugCheckParameter1]
push    [ebp+BugCheckCode]
call    _KeBugCheck2@24 ; KeBugCheck2(x,x,x,x,x,x)
pop     ebp
retn    14h
_KeBugCheckEx@20 endp</pre>
<p>Cette fonction est très courte, elle ne fait qu&rsquo;empiler des paramètres et appeler la routine <code>KeBugCheck2</code>. Cette dernière est par contre très longue. Comme mon but n&rsquo;est pas de la reverser intégralement mais juste isoler la partie qui m&rsquo;intéresse, je parcours rapidement son graphe avec IDA. Vu le nom des fonctions appelées, cette routine récupère des informations auprès des drivers sur le crash qui vient d&rsquo;avoir lieu.</p>
<h3>Couleur de fond</h3>
<p>A peu près au milieu de la fonction, on tombe sur ce code :</p>
<pre>call    _InbvAcquireDisplayOwnership@0 ; InbvAcquireDisplayOwnership()
call    _InbvResetDisplay@0 ; InbvResetDisplay()
push    4
push    1DFh
mov     ebx, 27Fh
push    ebx
push    esi
push    esi
call    _InbvSolidColorFill@20 ; InbvSolidColorFill(x,x,x,x,x)</pre>
<p>Apparemment, on a affaire à des appels de fonction gérant l&rsquo;affichage. On commence par faire un <em>reset </em>de l&rsquo;affichage, puis on appelle la routine <code>InvbSolidColorFill</code>. Les paramètres qui lui sont passés sont les suivants (je rappelle que l&rsquo;ordre est inversé, selon la convention d&rsquo;appel utilisé) :</p>
<ul>
<li>deux arguments égaux à 0 (si on cherche un peu plus haut on trouve un <code>xor esi,esi</code> donc esi = 0)</li>
<li>0x27F = 639</li>
<li>0x1DF = 479</li>
<li>4</li>
</ul>
<p>Etant astucieux, on se rend compte que 639 = 640 &#8211; 1 et 479 = 480 &#8211; 1. Hors, 640 x 480 correspond exactement à la résolution de l&rsquo;écran bleu. Avec les deux paramètres 0 qui précèdent, ces valeurs correspondent aux bornes (gauche, haute, droite, et basse) de la zone de l&rsquo;écran à remplir. La couleur de remplissage est précisée par le dernier paramètre, qui vaut 4. En cherchant un peu sur Google, on se rend compte que le code couleur utilisé par cette fonction est sur 4 bits : IBGR soit Intensity, Blue, Green and Red. 4 en décimal équivaut à 0100 en binaire, soit 1 pour le bleu et 0 pour le reste. Voilà donc pourquoi l&rsquo;écran bleu est bleu ! <img src='https://www.segmentationfault.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Si on désassemble la fonction <code>InvbSolidColorFill</code>, on se rend compte qu&rsquo;assez rapidement, celle-ci appelle la fonction <code>VidSolidColorFill</code>. Il s&rsquo;agit d&rsquo;une fonction importée par ntoskrnl.exe depuis bootvid.dll, le module chargé de la vidéo lors du boot. Son code est relativement peu clair, aussi j&rsquo;ai préféré jeter un coup d&rsquo;oeil aux sources de ReactOS, qui est nettement plus lisible. Pour ceux qui ne connaissent pas, il s&rsquo;agit d&rsquo;un projet visant à recoder Windows en Open Source. Voici le code de la fonction dans ReactOS (qui doit être très similaire dans Windows) :</p>
<pre>VOID NTAPI
VidSolidColorFill(IN ULONG Left, IN ULONG Top,
                  IN ULONG Right, IN ULONG Bottom,
                  IN UCHAR Color)
{
    int y, x;

	for (y = Top; y &lt;= Bottom; y++)
	{

            for (x = Left; x &lt;= Right; x++)
            {
                //
                // Draw the pixel
                //
                VidpSetPixel(x, y, Color);
            }
	}
}</pre>
<p>Comme vous pouvez le voir, on peut difficilement faire plus simple. Une fonction chargée de fixer la couleur d&rsquo;un pixel est appelée dans une double boucle, ce qui a pour effet de remplir l&rsquo;écran.</p>
<h3>Couleur du texte</h3>
<p>Revenons au code de <code>KeBugCheck2</code>. Juste après le code qui remplit l&rsquo;écran, on a ceci :</p>
<pre>push    0Fh
call    _InbvSetTextColor@4 ; InbvSetTextColor(x)</pre>
<p>Vu le nom de la fonction, on peut se douter que celle-ci fixe la couleur du texte du BSOD. 0xF = 0b1111 = blanc. Que demander de plus ?</p>
<h3>Patching à chaud pour changer les couleurs</h3>
<p>Que diriez vous de personnaliser la couleur du BSOD sur votre système ? Je n&rsquo;ai personnellement jamais aimé le bleu, je préfère le rouge (plus sexy pour un message d&rsquo;erreur !). Quand au texte, du jaune devrait faire l&rsquo;affaire&#8230;</p>
<p>Pour des raisons de sécurité, je vous déconseille fortement d&rsquo;éditer l&rsquo;exécutable de votre noyau (ntoskrnl.exe) car une erreur peut être vraiment fatale. Je préfère nettement opérer en mémoire, afin qu&rsquo;un simple reboot efface les modifications. D&rsquo;autre part, comme on s&rsquo;apprête à débugger Windows et à le faire planter, il faut opérer sur une deuxième machine. Munissez vous d&rsquo;une VM (VirtualBox pour ma part) que vous bootez en mode debug (flag /debug du boot.ini) avec Windbg lancée en parallèle sur votre machine. Pour ceux qui débutent, je vous conseille de lire <a href="http://0vercl0k.blogspot.com/2008/02/first-steps-into-ring0.html">cet article</a> d&rsquo;0vercl0ck qui vous expliquera comment tout configurer comme il faut. Prêt ? C&rsquo;est parti !</p>
<p>Une fois Windows démarré, freezez le avec Ctrl+Pause sous Windbg. Commençons par localiser ou se trouve l&rsquo;appel à <code>InvbSolidColorFill</code> dans <code>KeBugCheck2</code>. Sous IDA, on effectue un petit calcul d&rsquo;offset pour savoir ou se trouve le <code>push 4</code> par rapport au début de <code>KeBugCheck2</code>. Chez moi, j&rsquo;ai un offset de 0x60D. Dans Windbg, je tape donc <code>u KeBugCheck2+0x60d</code>. Cependant, ça ne tombe pas juste ; j&rsquo;atterris après ce bout de code (facile de le constater avec IDA). C&rsquo;est sans doute à cause du fait que j&rsquo;ai des mises à jours différentes sur ma machine et dans ma VM. Je remonte donc petit à petit et je finir par arriver sur l&rsquo;instruction qui m&rsquo;intéresse :</p>
<pre>kd&gt; u KeBugCheck2+0x5e4
nt!KeBugCheck2+0x5e4:
8053342e e8da9fffff      call    nt!InbvResetDisplay (8052d40d)
80533433 6a04            push    4
80533435 68df010000      push    1DFh
8053343a bb7f020000      mov     ebx,27Fh
8053343f 53              push    ebx
80533440 56              push    esi
80533441 56              push    esi
80533442 e84aa0ffff      call    nt!InbvSolidColorFill (8052d491)</pre>
<p>Il suffit de patcher la valeur située à l&rsquo;adresse <code>80533434</code> pour changer la couleur du BSOD ! Pour ma part, je choisis le rouge, soit 0b0001 = 1. Je tape donc ceci dans Windbg (eb = edit byte) :</p>
<pre>kd&gt; eb 80533434 01</pre>
<p>Vérifions cela en désassemblant à nouveau :</p>
<pre>kd&gt; u KeBugCheck2+0x5e4
nt!KeBugCheck2+0x5e4:
8053342e e8da9fffff      call    nt!InbvResetDisplay (8052d40d)
80533433 6a01            push    1
80533435 68df010000      push    1DFh
8053343a bb7f020000      mov     ebx,27Fh
8053343f 53              push    ebx
80533440 56              push    esi
80533441 56              push    esi
80533442 e84aa0ffff      call    nt!InbvSolidColorFill (8052d491)</pre>
<p>Super, ça semble avoir marché. Faisons de même avec la couleur du texte !</p>
<pre>kd&gt; u KeBugCheck2+0x5fd
nt!KeBugCheck2+0x5fd:
80533447 6a0f            push    0Fh
80533449 e8d1a0ffff      call    nt!InbvSetTextColor (8052d51f)</pre>
<p>Je veux du jaune, je remplace donc le 0x0F par un 0x0B soit 0b1011 (mélange clair de vert et rouge).</p>
<pre>kd&gt; eb 80533448 0B
kd&gt; u KeBugCheck2+0x5fd
nt!KeBugCheck2+0x5fd:
80533447 6a0b            push    0Bh
80533449 e8d1a0ffff      call    nt!InbvSetTextColor (8052d51f)</pre>
<p>Nikel. Il ne reste plus qu&rsquo;à provoquer un écran bleu ! Pour ce faire à partir de Windbg, c&rsquo;est très simple : il suffit de mettre eip à 0. On tape juste <code>r eip = 0</code> et on débloque la machine avec la commande <code>g</code>. Windows crashe instantanément et Windbg reprend la main. Attendez quelques secondes que les symboles se rechargent, appuyez sur Ctrl+Pause pour arrêter l&rsquo;opération de diagnostique de crash, et retapez <code>g</code> pour voir apparaître votre beau BSOD personnalisé. En ce qui me concerne, c&rsquo;est plutôt un RSOD (Red Screen Of Death) !</p>
<div id="attachment_635" class="wp-caption aligncenter" style="width: 310px"><a href="http://www.segmentationfault.fr/wp-content/uploads/2009/04/rsod.png"><img class="size-medium wp-image-635" title="RSOD" src="http://www.segmentationfault.fr/wp-content/uploads/2009/04/rsod-300x225.png" alt="Red Screen Of Death" width="300" height="225" /></a><p class="wp-caption-text">Red Screen Of Death</p></div>
<p>C&rsquo;est ti pas mignon tout ça ? Pour ceux qui par malheur verraient leur écran s&rsquo;afficher puis disparaître à cause d&rsquo;un reboot instantané, refaites la manip en n&rsquo;oubliant pas au préalable de désactiver les reboots sur crash système en faisant un clic droit sur le poste de travail, propriétés, avancé, paramètres de démarrage et récupération, puis décochez la case &laquo;&nbsp;redémarrer automatiquement&nbsp;&raquo;.</p>
<h3>Conclusion</h3>
<p>J&rsquo;espère que cet article vous aura plu et que vous allez dès à présent tuner votre BSOD pour rendre jaloux vos amis. Pour ceux qui voudraient aller plus loin, sachez qu&rsquo;il est également possible de modifier le texte affiché sur l&rsquo;écran&#8230; Après, vous pouvez intégrer tout ça dans un driver qui se chargera d&rsquo;effectuer le patching lui-même. C&rsquo;est ce que fait le programme BSOD Hack (cf en bas). Et si vous en avez le courage, vous pouvez même faire un petit hook inline (detour patching) afin de rediriger un crash vers votre propre routine. J&rsquo;ai entendu dire que certains avaient réussi à charger et afficher une image de bière lors d&rsquo;un crash, si ça peut vous donner des idées&#8230; <img src='https://www.segmentationfault.fr/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<h3>Références</h3>
<ul>
<li><a href="http://www.rohitab.com/discuss/index.php?showtopic=23767">BSOD Hack</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://www.segmentationfault.fr/securite-informatique/tunnez-votre-bsod-blue-screen-of-death/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Plongeon dans les appels systèmes Windows</title>
		<link>https://www.segmentationfault.fr/reversing/plongeon-dans-les-appels-systemes-windows/</link>
		<comments>https://www.segmentationfault.fr/reversing/plongeon-dans-les-appels-systemes-windows/#comments</comments>
		<pubDate>Sat, 28 Mar 2009 10:53:26 +0000</pubDate>
		<dc:creator>Emilien Girault</dc:creator>
				<category><![CDATA[Reverse Engineering]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[ring 0]]></category>

		<guid isPermaLink="false">http://www.segmentationfault.fr/?p=593</guid>
		<description><![CDATA[Mon stage m&#8217;a donné l&#8217;occasion d&#8217;analyser en détails comment un appel système est réalisé sous Windows. Si vous vous demandez comment un programme utilisateur (ring 3) fait pour appeler une fonction s&#8217;exécutant en mode noyau (ring 0), alors cet article est pour vous. J&#8217;y explique toutes les étapes de la chaîne en partant du début [...]]]></description>
			<content:encoded><![CDATA[<p>Mon stage m&rsquo;a donné l&rsquo;occasion d&rsquo;analyser en détails comment un appel système est réalisé sous Windows. Si vous vous demandez comment un programme utilisateur (ring 3) fait pour appeler une fonction s&rsquo;exécutant en mode noyau (ring 0), alors cet article est pour vous. J&rsquo;y explique toutes les étapes de la chaîne en partant du début (l&rsquo;appel de fonction dans un programme quelconque) pour arriver au code de l&rsquo;API en mode noyau. Pour lire cet article, des bases d&rsquo;assembleur sont indispensables.<span id="more-593"></span></p>
<h3>Privilèges des processeurs x86</h3>
<p>Avant d&rsquo;aborder le sujet, il est nécessaire de bien comprendre une subtilité des processeurs x86 : les niveaux de privilèges. Un processeur est censé exécuter du code machine produit par un compilateur / assembleur. Le code qui s&rsquo;exécute à un instant t a accès à certains privilèges qui dépendent du niveau de privilège dans lequel se trouve le processeur. Ce niveau, aussi appelé <em>ring</em>, ou <em>anneau</em>, est un entier codé sur deux bits. Il peut donc prendre 4 valeurs : 0, 1, 2, et 3. Plus ce nombre est petit, plus les privilèges sont élevés ; plus il est grand, plus ils sont restreints. Le ring 0 est appelé <em>mode superviseur</em>, ou <em>kernelland</em>,et c&rsquo;est sous ce mode que tournent 99% des noyaux d&rsquo;OS (Windows et Linux en font partie). Les programmes utilisateurs s&rsquo;exécutent quant à eux en ring 3, appelé <em>mode utilisateur</em> ou <em>userland</em>.</p>
<p>Le niveau de privilège courant du processeur est appelé CPL (Current Privilege Level). En interne, le CPL est stocké dans les deux premiers bits des registres CS et SS. La règle générale est la suivante : il n&rsquo;est pas possible d&rsquo;exécuter des instructions nécessitant un niveau de privilège inférieur au CPL. De même, il n&rsquo;est pas directement possible de demander au processeur de changer le CPL vers un niveau inférieur. On peut donc se demander : comment est-ce possible d&rsquo;appeler une routine du noyau depuis un code utilisateur ? C&rsquo;est là tout l&rsquo;objet de cet article&#8230;</p>
<h3>Un programme d&rsquo;exemple</h3>
<p>Afin d&rsquo;analyser comment se déroule un appel système, nous allons commencer par coder un programme d&rsquo;exemple en C.</p>
<pre>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;windows.h&gt;

int main(int argc, char *argv[])
{
    HANDLE handle;
    WIN32_FIND_DATA findData;

    handle = FindFirstFile(".", &amp;findData);

    printf("Handle = %dn", handle);

    getchar();

    return 0;
}</pre>
<p>Ce programme appelle l&rsquo;API Windows <code>FindFirstFile</code>. Cette fonction prend en paramètre un nom de dossier et retourne un handle vers le premier fichier de ce dossier. Je suis d&rsquo;accord : ce programme ne sert pas à grand chose, à part appeler une API Windows, mais c&rsquo;est justement le but.</p>
<h3>Tracing en userland</h3>
<p>Pour information, j&rsquo;ai réalisé tous ces tests sur un Windows XP SP3 français, avec Dev-C++ et GCC 3.4.2. Une fois le programme compilé, on peut utiliser un débogeur pour suivre l&rsquo;exécution du programme. Lançons OllyDbg et observons le <code>main()</code> du programme :</p>
<pre>00401290  /$ 55             PUSH EBP
00401291  |. 89E5           MOV EBP,ESP
00401293  |. 81EC 78010000  SUB ESP,178
00401299  |. 83E4 F0        AND ESP,FFFFFFF0
0040129C  |. B8 00000000    MOV EAX,0
004012A1  |. 83C0 0F        ADD EAX,0F
004012A4  |. 83C0 0F        ADD EAX,0F
004012A7  |. C1E8 04        SHR EAX,4
004012AA  |. C1E0 04        SHL EAX,4
004012AD  |. 8985 A4FEFFFF  MOV DWORD PTR SS:[EBP-15C],EAX
004012B3  |. 8B85 A4FEFFFF  MOV EAX,DWORD PTR SS:[EBP-15C]
004012B9  |. E8 82040000    CALL syscalls.00401740
004012BE  |. E8 1D010000    CALL syscalls.004013E0
004012C3  |. 8D85 A8FEFFFF  LEA EAX,DWORD PTR SS:[EBP-158]           ; |
004012C9  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; |
004012CD  |. C70424 0030400&gt;MOV DWORD PTR SS:[ESP],syscalls.00403000 ; |ASCII "fg"
004012D4  |. E8 E7050000    CALL &lt;JMP.&amp;KERNEL32.FindFirstFileA&gt;      ; FindFirstFileA
004012D9  |. 83EC 08        SUB ESP,8
004012DC  |. 8945 F4        MOV DWORD PTR SS:[EBP-C],EAX             ; |
004012DF  |. 8B45 F4        MOV EAX,DWORD PTR SS:[EBP-C]             ; |
004012E2  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; |
004012E6  |. C70424 0330400&gt;MOV DWORD PTR SS:[ESP],syscalls.00403003 ; |ASCII "Handle = %d"
004012ED  |. E8 4E050000    CALL &lt;JMP.&amp;msvcrt.printf&gt;                ; printf
004012F2  |. E8 39050000    CALL &lt;JMP.&amp;msvcrt.getchar&gt;               ; [getchar
004012F7  |. B8 00000000    MOV EAX,0
004012FC  |. C9             LEAVE
004012FD  . C3             RETN</pre>
<p>Je rappelle que nous sommes à présent dans un programme utilisateur, donc en userland. Il est facile de le vérifier en regardant la valeur de CS. Chez moi, c'est 0x2B, soit 0b101011. Les deux premiers bits (poids faible) sont bien 0b11, correspondant au ring 3.</p>
<p>L'appel à <code>FindFirstFile</code> se trouve en <code>004012D4</code>, après que les paramètres aient été mis sur la pile. Après avoir placé un breakpoint sur le call (F2), et lancé l'exécution (F9), le programme s'arrête dessus. On fait un <em>step in</em> (F7) pour suivre le call.</p>
<pre>004018C0   $-FF25 B8504000  JMP DWORD PTR DS:[&lt;&amp;KERNEL32.FindFirstFileA&gt;]</pre>
<p>Nous nous situons à présent dans une zone appelée <em>trampoline</em>. Cette zone fait référence à l&rsquo;IAT (Import Address Table) de l&rsquo;exécutable, qui contient les adresses des fonctions importées. Pour faire le parallèle avec Linux, le trampoline est l&rsquo;équivalent de la section .plt, et l&rsquo;IAT joue le même rôle que la section .got. Pour plus d&rsquo;informations sur ces sections je vous conseille de lire <a href="http://www.segmentationfault.fr/linux/role-plt-got-ld-so/">cet article</a>.</p>
<p>Le jump fait référence à un pointeur situé en 004050B8, qui contient l&rsquo;adresse de la fonction <code>FindFirstFileA</code>. On rappuie sur F7 pour suivre l&rsquo;appel. On arrive alors dans la section .text de kernel32.dll, qui a été chargée à l&rsquo;exécution.</p>
<pre>7C813869 &gt; 8BFF             MOV EDI,EDI
7C81386B   55               PUSH EBP
7C81386C   8BEC             MOV EBP,ESP
7C81386E   81EC 6C020000    SUB ESP,26C
...
7C813894   56               PUSH ESI
7C813895   56               PUSH ESI
7C813896   56               PUSH ESI
7C813897   8D8D ACFDFFFF    LEA ECX,DWORD PTR SS:[EBP-254]
7C81389D   51               PUSH ECX
7C81389E   56               PUSH ESI
7C81389F   FF70 04          PUSH DWORD PTR DS:[EAX+4]
7C8138A2   E8 66B2FFFF      CALL kernel32.FindFirstFileExW</pre>
<p>On s&rsquo;aperçoit que cette fonction en appelle une autre, <code>FindFirstFileExW</code>, toujours dans kernel32. Pourquoi ? Simplement parce que la plupart des fonctions internes de Windows utilisent un encodage Unicode, et non pas ASCII. Dans la convention Windows, les fonctions se terminant par un A gèrent l&rsquo;ASCII, et celles en W gèrent l&rsquo;Unicode. En interne, les fonctions ASCII convertissent les paramètres passés en Unicode, et appèlent les fonctions Unicode correspondantes. C&rsquo;est le call que nous venons de voir. Suivons le.</p>
<pre>7C80EB0D &gt; 8BFF             MOV EDI,EDI
7C80EB0F   55               PUSH EBP
7C80EB10   8BEC             MOV EBP,ESP
7C80EB12   81EC CC020000    SUB ESP,2CC
...
7C80EC66   57               PUSH EDI
7C80EC67   8D85 90FDFFFF    LEA EAX,DWORD PTR SS:[EBP-270]
7C80EC6D   89B5 40FDFFFF    MOV DWORD PTR SS:[EBP-2C0],ESI
7C80EC73   8B35 1410807C    MOV ESI,DWORD PTR DS:[&lt;&amp;ntdll.NtOpenFile&gt;] ; ZwOpenFile
7C80EC79   50               PUSH EAX
7C80EC7A   C785 34FDFFFF 18&gt;MOV DWORD PTR SS:[EBP-2CC],18
7C80EC84   FFD6             CALL ESI
...
7C80ED46   FFB5 90FDFFFF    PUSH DWORD PTR SS:[EBP-270]
7C80ED4C   FF15 2812807C    CALL DWORD PTR DS:[&lt;&amp;ntdll.NtQueryDirectoryFile&gt;]</pre>
<p>On voit que cette fonction réalise plusieurs appels (ici je n&rsquo;en n&rsquo;ai affiché que 2 mais il y en a d&rsquo;autres) dans ntdll.dll, une autre DLL chargée. Continuons notre traçing en explorant l&rsquo;appel à <code>ZwOpenFile</code>.</p>
<pre>7C91D580 &gt; B8 74000000      MOV EAX,74
7C91D585   BA 0003FE7F      MOV EDX,7FFE0300
7C91D58A   FF12             CALL DWORD PTR DS:[EDX]  ; <code>ntdll.KiFastSystemCall</code>
7C91D58C   C2 1800          RETN 18</pre>
<h3>Transition vers le mode noyau</h3>
<p>Nous sommes à présent dans ntdll.dll, dans la fonction <code>ZwOpenFile</code> exportée par la dll. Comme vous le voyez, la fonction est très courte. La première instruction place 0&#215;74 dans EAX, qui correspond au numéro de la fonction du noyau qui va être appelée. On a ensuite un appel à une fonction nommée <code>KiFastSystemCall</code>. C&rsquo;est elle qui va réaliser le passage en mode noyau, à l&rsquo;aide des instructions suivantes :</p>
<pre>7C91E4F0 &gt; 8BD4             MOV EDX,ESP
7C91E4F2   0F34             SYSENTER
7C91E4F4 &gt; C3               RETN</pre>
<p>C&rsquo;est précisément SYSENTER qui réalise la transition. Mais que fait donc cette instruction ? On ouvre le manuel Intel 2B au chapitre 4.1, instruction SYSENTER. On y apprend que cette instructionpermet d&rsquo;exécuter des appels systèmes. Historiquement, les appels systèmes étaient exécutés en utilisant les interruptions logicielles, avec l&rsquo;instruction INT 2E sous Windows (et INT 80 sous Linux). SYENTER étant plus rapide, elle a succédé à l&rsquo;ancienne méthode, qui reste toutefois disponible pour des raisons de compatibilité.</p>
<p>Contrairement aux interruptions, SYSENTER n&rsquo;utilise pas de table de pointeurs, mais des registres spéciaux du processeur, appelés MSR (pour Model Specific Register). Ces registres sont assez particuliers ; ils possèdent un numéro et sont accessibles en lecture et écriture via les instructions <code>rdmsr</code> et <code>wrmsr</code>. SYSENTER en utilise 3 :</p>
<ul>
<li>IA32_SYSENTER_CS (0&#215;174) correspond à la valeur à charger dans CS quand SYSENTER sera appelé</li>
<li>IA32_SYSENTER_ESP (0&#215;175) sera quant à lui chargé dans ESP</li>
<li>IA32_SYSENTER_EIP (0&#215;176) sera chargé dans EIP</li>
</ul>
<p>Ainsi, ces trois registres définissent tout ce qu&rsquo;il faut pour exécuter un bout de code en mode en mode noyau, puisque CS (et donc ses deux premiers bits, encodant le CPL) sera changé.</p>
<p>Ces registres ne sont pas lisibles en mode utilisateur ; il faut impérativement être en ring 0 pour les lire. Pour cela, utilisons Windbg, le débogueur noyau de Microsoft. Il se télécharge <a href="http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx#a">ici</a>, vous trouverez également des informations pour la configuration des symboles sur <a href="http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx#a">cette page</a>.</p>
<p>Lançons Windbg en local kernel debugging (Cltrl K puis local). Dans l&rsquo;invite de commande, tapons :</p>
<pre>lkd&gt; rdmsr 174
msr[174] = 00000000`00000008
lkd&gt; rdmsr 176
msr[176] = 00000000`80541520</pre>
<p>Nous voyons donc que IA32_SYSENTER_CS = 0&#215;8 (donc avec un niveau de privilège à 0) et IA32_SYSENTER_EIP = 80541520. Au passage, on notera que cette adresse est supérieur à 80000000, c&rsquo;est à dire en mode noyau, puisque Windows divise l&rsquo;espace d&rsquo;adressage de tout processus en 2 : 2 Go pour l&rsquo;utilisateur (de 00000000 à 7FFFFFFF) et 2 Go pour le noyau (de 80000000 à FFFFFFFF). Regardons ce qui se trouve en 80541520 :</p>
<pre>lkd&gt; u 80541520 80541520+100     //desassemble les 100 premiers octets
nt!KiFastCallEntry:
80541520 b923000000      mov     ecx,23h
80541525 6a30            push    30h
80541527 0fa1            pop     fs
80541529 8ed9            mov     ds,cx
8054152b 8ec1            mov     es,cx
...
80541600 8b3f            mov     edi,dword ptr [edi]
80541602 8b1c87          mov     ebx,dword ptr [edi+eax*4]
80541605 2be1            sub     esp,ecx
80541607 c1e902          shr     ecx,2
8054160a 8bfc            mov     edi,esp
8054160c 3b3534215680    cmp     esi,dword ptr [nt!MmUserProbeAddress (80562134)]
80541612 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (805417c0)
80541618 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8054161a ffd3            call    ebx</pre>
<p>On voit que la fonction s&rsquo;appelle <code>KiFastCallEntry</code> et se trouve dans le module nt qui correspond à ntoskrnl.exe, un des exécutables du noyau.</p>
<p>Après plusieurs vérifications de paramètres, la fonction <code>KiFastCallEntry</code> charge une table dans EDI (après les &laquo;&nbsp;&#8230;&nbsp;&raquo;). Il s&rsquo;agit de la SSDT (Service System Dispatch Table), une table très importante du noyau qui a un rôle similaire à la table d&rsquo;interruptions (IDT). Elle sert à dispatcher les appels systèmes vers la bonne fonction. Le symbole correspondant à la SSDT se nomme <code>KiServiceTable</code>, et est exporté par le noyau. Voici le début de son contenu :</p>
<pre>lkd&gt; dds KiServiceTable
80504450  805a4614 nt!NtAcceptConnectPort
80504454  805f0adc nt!NtAccessCheck
80504458  805f4312 nt!NtAccessCheckAndAuditAlarm
8050445c  805f0b0e nt!NtAccessCheckByType
80504460  805f434c nt!NtAccessCheckByTypeAndAuditAlarm</pre>
<p>Souvenez-vous : juste avant le SYSENTER, on a placé le numéro d&rsquo;appel système dans EAX. Ce registre va être utilisé ici afin de servir d&rsquo;index dans cette table et de trouver le pointeur de la fonction à appeler. L&rsquo;instruction suivante multiplie justement EAX par 4 (la taille d&rsquo;une entrée dans la table) et l&rsquo;ajoute à l&rsquo;adresse de début de la table contenu dans EDI. Le résultat est place dans EBX. Et quelques instructions plus loin, on trouve&#8230; un call EBX <img src='https://www.segmentationfault.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Faisons le calcul nous même : nous connaissons le numéro d&rsquo;appel système (0&#215;74) :</p>
<pre>lkd&gt; dds KiServiceTable+0x74*4
80504620  8057a182 nt!NtOpenFile</pre>
<p>Bingo, voila la fonction <code>NtOpenFile</code> : c&rsquo;est elle qui sera exécutée lors du call EBX.</p>
<h3>Retour en userland</h3>
<p>Notre reversing peut s&rsquo;arrêter ici. Nous n&rsquo;allons pas désassembler cette fonction car ce n&rsquo;est pas le but de l&rsquo;article. Concernant le retour de l&rsquo;appel système, on peut, en désassemblant la suite de <code>KiFastCallEntry</code> , voir que la fonction <code>KiServiceExit</code> va être appelée. A son tour, elle appelle une des deux fonctions <code>KiSystemCallExit</code> ou <code>KiSystemCallExit</code>2. La première revient en mode utilisateur en utilisant l&rsquo;instruction IRET, et l&rsquo;autre le fait en utilisant SYSEXIT (c&rsquo;est celle là qui sera appelée dans notre cas). Cette instruction rebascule en userland et restaure EIP. On se retrouve alors dans <code>ZwOpenFile</code>, de ntdll.</p>
<h3>Conclusion</h3>
<p>Au travers de cet article, nous avons tracé l&rsquo;exécution d&rsquo;un programme utilisateur afin de comprendre comment il passe en mode noyau afin d&rsquo;exécuter des instructions privilégiées.  Nous avons vu comment fonctionne l&rsquo;instruction SYSENTER et le rôle de la SSDT.</p>
<p>On peut alors imaginer plusieurs hooks possibles exploitant ce schéma de fonctionnement. Au niveau utilisateur, on peut hooker l&rsquo;entrée de l&rsquo;IAT correspondant à la fonction à appeler. En mode noyau, il y a plusieurs possibilités. La première serait de remplacer l&rsquo;adresse de la fonction à appeler dans la SSDT par une autre fonction qui appelle l&rsquo;originale et effectue un filtrage en entrée et en sortie. Une autre serait de modifier carrément le registre IA32_SYSENTER_EIP en le faisant pointer sur une autre routine de traitement. L&rsquo;équivalent pour les vieilles versions de Windows utilisant les interruptions serait de modifier le registre IDTR contenant l&rsquo;adresse de la table des interruptions, ou bien de hooker l&rsquo;entrée 2E dans cette table. Enfin, une dernière solution, radicale mais fonctionnelle, serait de faire un hook inline de la fonction pointée, en remplaçant ses premiers octets par un call d&rsquo;une autre fonction. Comme vous le voyez, les possibilités de hook sont innombrables, les plus &laquo;&nbsp;profondes&nbsp;&raquo; étant bien souvent les plus indétectables&#8230;</p>
<h3>Références</h3>
<ul>
<li><a href="http://www.ivanlef0u.tuxfamily.org/?p=63">Ivanlef0u&rsquo;s Blog &#8211; SSDT Hooking Reinvented</a></li>
<li><a href="http://www.ivanlef0u.tuxfamily.org/?p=22">Ivanlef0u&rsquo;s Blog &#8211; SYSENTER, stepping into da ring0</a></li>
<li><a href="http://www.amazon.com/Rootkits-Subverting-Addison-Wesley-Software-Security/dp/0321294319">Rootkits &#8211; Subverting the Windows Kernel, Greg Hoglund et Jamie Butler<br />
</a></li>
<li><a href="http://download.intel.com/design/processor/manuals/253668.pdf">Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 2B</a>, Chapitre 4.1, Instruction SYSENTER</li>
<li><a href="http://download.intel.com/design/processor/manuals/253668.pdf">Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A</a>, Chapitre 4.5</li>
<li><a href="http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx#a">Debugging Tools for Windows</a></li>
<li><a href="http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx#a">Debugging Tools and Symbols: Getting Started</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://www.segmentationfault.fr/reversing/plongeon-dans-les-appels-systemes-windows/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Stage chez Thomson R&amp;D</title>
		<link>https://www.segmentationfault.fr/projets/stage-chez-thomson-rd/</link>
		<comments>https://www.segmentationfault.fr/projets/stage-chez-thomson-rd/#comments</comments>
		<pubDate>Wed, 25 Mar 2009 14:48:10 +0000</pubDate>
		<dc:creator>Emilien Girault</dc:creator>
				<category><![CDATA[Projets]]></category>
		<category><![CDATA[Reverse Engineering]]></category>
		<category><![CDATA[ring 0]]></category>

		<guid isPermaLink="false">http://www.segmentationfault.fr/?p=587</guid>
		<description><![CDATA[J&#8217;ai commencé mon stage il y a maintenant une dizaine de jours chez Thomson R&#38;D à Cesson-Sévigné. Comme prévu, mon stage a pour sujets principaux le reverse-engineering et le développement de drivers.  Pour être plus précis, le but du stage est de réaliser un conteneur sécurisé de données. Autrement dit, un module qui va contenir [...]]]></description>
			<content:encoded><![CDATA[<p>J&rsquo;ai commencé mon stage il y a maintenant une dizaine de jours chez Thomson R&amp;D à Cesson-Sévigné. Comme prévu, mon stage a pour sujets principaux le reverse-engineering et le développement de drivers.  Pour être plus précis, le but du stage est de réaliser un conteneur sécurisé de données. Autrement dit, un module qui va contenir des données très sensibles et dont le but va être de les rendre inaccessibles de l&rsquo;extérieur. Il s&rsquo;agit d&rsquo;un projet purement orienté recherche, et à part mon maître de stage, je suis seul à travailler dessus.<span id="more-587"></span></p>
<p>Au programme : plongeon dans les bas fonds de l&rsquo;architecture x86 d&rsquo;Intel, et plus précisément toutes les fonctionnalités de protection, dont certaines sont parfois méconnues (ou pas). Ce n&rsquo;est pas sans me rappeler certaines parties de mes cours de système suivis à l&rsquo;INSA et au RIT, mais avec une forte dose de concret en plus&#8230;</p>
<p>Cette fois-ci, une grosse partie du code s&rsquo;exécutera en mode noyau (ring 0), ce qui va me faire goûter aux joies de la programmation de drivers et des <em>Blue Screen Of Death</em> de Windows. D&rsquo;ailleurs au niveau de l&rsquo;OS, je travaille principalement Windows pour le moment, mais dans l&rsquo;idéal, le projet devrait aussi être porté sous Linux.</p>
<p>De plus, qui dit sécurité des données et confidentialité dit&#8230; cryptographie ! Le module que je vais développer devra intégrer des primitives de base permettant d&rsquo;encrypter les données sensibles et de les décrypter au moment voulu.</p>
<p>Enfin, ce stage va être l&rsquo;occasion pour moi de redécouvrir les techniques utilisées par les rootkits, principalement le hooking et la furtivité. Autant dire que je me sens comme un poisson dans l&rsquo;eau&#8230;</p>
<p>Bien entendu, ayant signé une charte de confidentialité, je ne pourrais pas divulguer tout ce que je fais dans les moindres détails, mais je compte bien publier ici quelques articles concernant des sujets ou projets du domaine public que mon stage m&rsquo;aura fait découvrir.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.segmentationfault.fr/projets/stage-chez-thomson-rd/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Reversing Linux : Comprendre le rôle des sections PLT et GOT dans l&#8217;édition de liens dynamique</title>
		<link>https://www.segmentationfault.fr/linux/role-plt-got-ld-so/</link>
		<comments>https://www.segmentationfault.fr/linux/role-plt-got-ld-so/#comments</comments>
		<pubDate>Fri, 25 Jul 2008 09:04:58 +0000</pubDate>
		<dc:creator>Emilien Girault</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Reverse Engineering]]></category>
		<category><![CDATA[hacking]]></category>

		<guid isPermaLink="false">http://www.segmentationfault.fr/?p=41</guid>
		<description><![CDATA[J&#8217;ai récemment eu la volonté de comprendre plus en détails comment Linux parvient à résoudre les symboles (tels que les fonctions) liés dynamiquement dans les programmes. Ne disposant pas d&#8217;Internet et n&#8217;ayant pas les sources du noyau sous la main, il a fallu faire avec les moyens du bord : un éditeur de texte, gcc, [...]]]></description>
			<content:encoded><![CDATA[<p>J&rsquo;ai récemment eu la volonté de comprendre plus en détails comment Linux parvient à résoudre les symboles (tels que les fonctions) liés dynamiquement dans les programmes. Ne disposant pas d&rsquo;Internet et n&rsquo;ayant pas les sources du noyau sous la main, il a fallu faire avec les moyens du bord : un éditeur de texte, gcc, gdb, et un peu de connaissance relative à l&rsquo;édition de liens en général. Je préfère donc préciser que cet article n&rsquo;a pas pour objectif d&rsquo;être exhaustif et de décrire le fonctionnement exact de l&rsquo;édition de liens dynamique et de la résolution des symboles sous Linux. Il se contente de présenter la démarche que j&rsquo;ai eu pour comprendre les choses à ma manière. En particulier, le rôle des sections PLT et GOT sera expliqué. Si le sujet vous intéresse, vous trouverez sans doute d&rsquo;autres papiers certainement plus détaillés.<span id="more-41"></span></p>
<h3>Rappels théoriques</h3>
<p>Tout d&rsquo;abord, il convient de faire quelques rappels sur la compilation et l&rsquo;édition de liens des binaires. Quand on compile un programme qui fait appel à des fonctions situées dans d&rsquo;autres bibliothèques (telles que la librairie standard), l&rsquo;édition de liens peut être faite de deux façons différentes. La première méthode, dite <em>statique</em>, consiste à intégrer à l&rsquo;exécutable toutes les librairies dont il a besoin pour fonctionner. A l&rsquo;exécution, tous les symboles sont donc résolus, et les appels sont immédiats. Si cette méthode a été la plus utilisée dans les versions anciennes des OS, elle est toutefois largement dépassée. En effet, il s&rsquo;agit d&rsquo;un gouffre à espace disque, puisqu&rsquo;elle oblige à dupliquer chaque librairie autant de fois qu&rsquo;il y a d&rsquo;exécutables qui l&rsquo;utilisent. Les exécutables générés sont donc volumineux, puisqu&rsquo;il suffit par exemple d&rsquo;un simple appel à <code>printf()</code> pour que toute la librairie standard du C soit intégrée à l&rsquo;exécutable !</p>
<p>Depuis les versions &laquo;&nbsp;récentes&nbsp;&raquo; de Linux, c&rsquo;est la deuxième méthode d&rsquo;édition de liens, dite <em>dynamique</em>, qui est utilisée par défaut. Avec cette méthode, chaque librairie est compilée une fois pour toute dans une librairie dynamique, ou <em>partagée</em> (<em>shared</em>) ayant l&rsquo;extension .so (équivalent des .dll sous Windows). Lorsque l&rsquo;on compile un programme qui y fait référence, on y insère juste le nom du symbole (fonction ou variable) dont il a besoin, ainsi que le nom de la librairie. C&rsquo;est à l&rsquo;exécution du programme que l&rsquo;<em>éditeur de liens dynamique</em> (ou <em>dynamic linker</em>), nommé <em>ld.so</em>, charge les libraires nécessaire et effectue la résolution des symboles manquants en temps réel. C&rsquo;est donc la vitesse d&rsquo;exécution qui s&rsquo;en retrouve pénalisée, même si nous verrons que cette perte est toutefois relative car compensée par un système de mise en cache des adresses.</p>
<p>Enfin, il convient de clarifier la notion de PIC, ou <em>Position Independant Code</em>. Un code exécutable est dit PIC s&rsquo;il peut être mappé à l&rsquo;importe quelle région mémoire tout en pouvant s&rsquo;exécuter convenablement. Dans de tels exécutables, aucune adresse absolue ne doit apparaître, puisque si l&rsquo;exécutable se retrouve translaté en mémoire, les adresses absolues ne seront plus valides. Dans Linux, les librairies dynamiques sont en PIC. C&rsquo;est le linker dynamique, ld.so, qui les charge en mémoire à l&rsquo;exécution, et leur place en mémoire peut varier d&rsquo;une exécution à une autre. Ainsi, l&rsquo;adresse des fonctions de la libraire standard, telles que <code>printf()</code>, changent de place à chaque exécution. Pourtant, un programme qui utilise <code>printf()</code> n&rsquo;est compilé qu&rsquo;une seule fois. Comment les processus arrivent-ils donc à s&rsquo;exécuter tout en prenant en compte cette variation d&rsquo;adresses ? C&rsquo;est là tout l&rsquo;objectif de cet article&#8230;</p>
<h3>Un programme de test</h3>
<p>Place à la pratique ! Dans la suite, je considérerais que nous somme sur une Ubuntu Hardy (noyau 2.6.24), avec gcc 4.2.3 et gdb 6.8. Nous allons utiliser le programme en C suivant :</p>
<pre>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

int main(){
  char c1[] = "chaine1";
  char c2[] = "chaine2";

  int res = strcmp(c1, c2);

  printf("c1 == c2 ? %dn", res);

  return 0;
}</pre>
<p>Pour illustrer ce que nous venons de voir, compilons-le avec les deux méthodes, statique et dynamique :</p>
<pre>$ gcc -o bin_str bin_str.c
$ gcc -static -o bin_str_static bin_str.c</pre>
<p>Notez au passage que gcc linke les exécutable dynamiquement par défaut, et que l&rsquo;option -static permet de forcer l&rsquo;édition de liens statique. A l&rsquo;exécution, les deux produisent exactement le même résultat :</p>
<pre>$ ./bin_str
c1 == c2 ? -1
$ ./bin_str_static
c1 == c2 ? -1</pre>
<p>Cependant, quand on compare la taille des exécutables&#8230;</p>
<pre>$ ls -lh bin_str bin_str_static
-rwxrwxrwx 1 root root 6,6K 2008-07-21 19:12 bin_str
-rwxrwxrwx 1 root root 545K 2008-07-21 19:12 bin_str_static</pre>
<p>On constate que le binaire lié statiquement (545K) est 80 fois plus volumineux que le binaire lié dynamiquement (qui ne pèse que 6.6K) ! En effet, c&rsquo;est toute la librairie standard qui a été incorporée au binaire durant l&rsquo;édition de liens.</p>
<p>Lançons-les avec gdb et désassemblons le main de chacun :</p>
<pre>$ gdb ./bin_str_static
(gdb) disas main
Dump of assembler code for function main:
...
0x0804823b &lt;main+75&gt;:   call   0x804dd70 &lt;strcmp&gt;
...
0x08048251 &lt;main+97&gt;:   call   0x8048c70 &lt;printf&gt;
...
(gdb) quit

$ gdb ./bin_str
(gdb) disas main
Dump of assembler code for function main:
...
0x0804844f &lt;main+75&gt;:   call   0x8048364 &lt;strcmp@plt&gt;
...
0x08048465 &lt;main+97&gt;:   call   0x8048344 &lt;printf@plt&gt;
...</pre>
<p>On constate que dans le binaire lié statiquement, <code>printf()</code> et <code>strcmp()</code> ont bien été intégrées dans la région .text de l&rsquo;exécutable. Dans le binaire lié dynamiquement, les deux appels n&rsquo;ont cependant pas lieu dans la .text, mais dans une région spécifique à l&rsquo;édition de lien dynamique : la .plt. La <em>PLT</em>, pour <em>Procedure Linkage Table</em>, est une table sertvant à faire le lien avec les fonctions situées dans des bibliothèques dynamiques.</p>
<h3>La PLT et la GOT</h3>
<p>Dans la suite, on ne travaillera évidemment qu&rsquo;avec le binaire lié dynamiquement. Listons les régions actuellement mapées dans l&rsquo;espace mémoire de l&rsquo;exécutable:</p>
<pre>(gdb) info files
...
0x080482e4 - 0x08048314 is .init
0x08048314 - 0x08048374 is .plt
0x08048380 - 0x0804852c is .text
0x0804852c - 0x08048548 is .fini
0x08048548 - 0x0804856f is .rodata
0x08048570 - 0x08048574 is .eh_frame
0x08049574 - 0x0804957c is .ctors
0x0804957c - 0x08049584 is .dtors
0x08049584 - 0x08049588 is .jcr
0x08049588 - 0x08049658 is .dynamic
0x08049658 - 0x0804965c is .got
0x0804965c - 0x0804967c is .got.plt
0x0804967c - 0x08049688 is .data
0x08049688 - 0x0804968c is .bss</pre>
<p>La région .plt est donc mappée entre les adresses 0&#215;08048314 et 0&#215;08048374. Il est important de noter que cette section se trouve à des adresses fixes. On vérifie au passage que les adresses appelées par les deux calls du main (0&#215;8048364  pour <code>strcmp</code> et 0&#215;8048344 pour <code>printf</code>) appartiennent bien à cette zone. Désassemblons à présent la région .plt :</p>
<pre>(gdb) disas 0x08048314 0x08048374
Dump of assembler code from 0x8048314 to 0x8048374:
0x08048314 &lt;_init+48&gt;:  pushl  0x8049660
0x0804831a &lt;_init+54&gt;:  jmp    *0x8049664
0x08048320 &lt;_init+60&gt;:  add    %al,(%eax)
0x08048322 &lt;_init+62&gt;:  add    %al,(%eax)
0x08048324 &lt;__gmon_start__@plt+0&gt;:      jmp    *0x8049668
0x0804832a &lt;__gmon_start__@plt+6&gt;:      push   $0x0
0x0804832f &lt;__gmon_start__@plt+11&gt;:     jmp    0x8048314 &lt;_init+48&gt;
0x08048334 &lt;__libc_start_main@plt+0&gt;:   jmp    *0x804966c
0x0804833a &lt;__libc_start_main@plt+6&gt;:   push   $0x8
0x0804833f &lt;__libc_start_main@plt+11&gt;:  jmp    0x8048314 &lt;_init+48&gt;
0x08048344 &lt;printf@plt+0&gt;:      jmp    *0x8049670
0x0804834a &lt;printf@plt+6&gt;:      push   $0x10
0x0804834f &lt;printf@plt+11&gt;:     jmp    0x8048314 &lt;_init+48&gt;
0x08048354 &lt;__stack_chk_fail@plt+0&gt;:    jmp    *0x8049674
0x0804835a &lt;__stack_chk_fail@plt+6&gt;:    push   $0x18
0x0804835f &lt;__stack_chk_fail@plt+11&gt;:   jmp    0x8048314 &lt;_init+48&gt;
0x08048364 &lt;strcmp@plt+0&gt;:      jmp    *0x8049678
0x0804836a &lt;strcmp@plt+6&gt;:      push   $0x20
0x0804836f &lt;strcmp@plt+11&gt;:     jmp    0x8048314 &lt;_init+48&gt;
End of assembler dump.</pre>
<p>Globalement, on constate que la section .plt est composée de plusieurs sous-sections de taille égale (0&#215;10 soit 16 octets), que nous appellerons <em>entrées</em>. L&rsquo;entrée 0 (composée des 4 premières instructions) est un peu spéciale ; nous y reviendrons plus tard. Les autres entrées, à compter de la 1 jusqu&rsquo;à la dernière, sont composées de 3 instructions qui suivent toujours le même schéma. Un premier <code>jmp</code> a lieu, dont l&rsquo;adresse de saut n&rsquo;est pas explicitement donnée dans l&rsquo;instruction, mais par l&rsquo;intermédiaire d&rsquo;un pointeur. Par exemple, dans le cas de l&rsquo;entrée 3 correspondant à <code>printf</code>, l&rsquo;adresse de saut se trouve dans les 4 octets pointés par l&rsquo;adresse 0&#215;8049670 qui fait donc office de pointeur. On remarque que la 2ème instruction de l&rsquo;entrée 0 comporte aussi un <code>jmp</code> avec pointeur. En regardant de plus près chaque pointeur, y compris celui de l&rsquo;entrée 0 (0&#215;8049664), on s&rsquo;aperçoit qu&rsquo;ils se trouvent tous les uns à la suite des autres. En effet, on a ici : 0&#215;8049664, 0&#215;8049668, 0x804966c, 0&#215;8049670, 0&#215;8049674, et 0&#215;8049678. Pour savoir où se trouvent ces pointeurs, il suffit de regarder à nouveau la liste des sections mappées en mémoire :</p>
<pre>(gdb) info files
...
0x0804957c - 0x08049584 is .dtors
0x08049584 - 0x08049588 is .jcr
0x08049588 - 0x08049658 is .dynamic
0x08049658 - 0x0804965c is .got  &lt;=====
0x0804965c - 0x0804967c is .got.plt</pre>
<p>Ces adresses se situent donc dans la section nommée .got.plt. Les régions nommées ici .got et .got.plt font en réalité partie d&rsquo;une table, que l&rsquo;on nomme la <em>GOT</em>, pour <em>Global Offset Table</em>. Le rôle de cette table sera explicité dans la suite.</p>
<p>Affichons maintenant son contenu. Étant donné que cette région contient des pointeurs, il s&rsquo;agit de données et non d&rsquo;instructions ; pour afficher son contenu il convient donc d&rsquo;utiliser l&rsquo;instruction <code>x</code> de gdb, et non pas <code>disas</code>. De plus, la commande précédente nous indique que la section fait 0&#215;20 = 32 octets de long, soit 8 pointeurs de 4 octets.</p>
<pre>(gdb) x/8x 0x0804965c
0x804965c &lt;_GLOBAL_OFFSET_TABLE_&gt;:      0x08049588      0x00000000
                                        0x00000000      0x0804832a
0x804966c &lt;_GLOBAL_OFFSET_TABLE_+16&gt;:   0x0804833a      0x0804834a
                                        0x0804835a      0x0804836a</pre>
<p>Je rappelle que pour le moment, le programme n&rsquo;a pas encore été lancé. Le contenu de cette section est fixe, du moins avant chaque exécution. Il est tout à fait possible de le voir en utilisant d&rsquo;autres outils tels que objdump.</p>
<p>Comme nous venons de le voir, les entrées de la PLT référencent des pointeurs situés dans la GOT. Pour comprendre le rôle de ces deux tables, regardons comment se déroule un appel à <code>strcmp()</code>. Cette fonction est située dans l&rsquo;entrée 3 de la PLT :</p>
<pre>0x08048364 &lt;strcmp@plt+0&gt;:      jmp    *0x8049678
0x0804836a &lt;strcmp@plt+6&gt;:      push   $0x20
0x0804836f &lt;strcmp@plt+11&gt;:     jmp    0x8048314 &lt;_init+48&gt;</pre>
<p>Le 1er jmp référence un pointeur (0&#215;8049678) situé dans la GOT. Voyons ce qu&rsquo;il contient :</p>
<pre>(gdb) x 0x8049678
0x8049678 &lt;_GLOBAL_OFFSET_TABLE_+28&gt;:   0x0804836a</pre>
<p>Sa valeur veut 0x0804834a, qui correspond&#8230; à une adresse de la PLT, et plus précisément à l&rsquo;adresse de l&rsquo;instruction juste après le <code>jmp</code>. En d&rsquo;autres termes, lorsque <code>strcmp()</code> sera appelée, on saute dans la PLT, et on exécute le 1er <code>jmp</code>, qui se contente de sauter sur l&rsquo;instruction suivante. Cela peut paraître inutile à première vue, mais nous verrons l&rsquo;astuce qui se cache derrière plus tard. Continuons le fil de l&rsquo;exécution : après ce 1er <code>jmp</code>, on rencontre un <code>push</code>, qui empile une certaine valeur, ici 0&#215;20. Enfin, on rencontre un <code>jmp</code>, et en examinant l&rsquo;adresse de saut on s&rsquo;aperçoit qu&rsquo;il nous emmène sur l&rsquo;entrée 0 de la PLT.</p>
<p>Une fois sur l&rsquo;entrée 0, on rencontre deux instructions (les deux suivantes ne sont pas vraiment des instructions, mais correspondent aux opcodes 0&#215;0000) :</p>
<pre>0x08048314 &lt;_init+48&gt;:  pushl  0x8049660
0x0804831a &lt;_init+54&gt;:  jmp    *0x8049664</pre>
<p>On commence par empiler une valeur, qui se révèle être l&rsquo;entrée 1 de la GOT. On effectue ensuite un <code>jmp</code> sur le contenu d&rsquo;un pointeur, se situant lui aussi dans la GOT mais à l&rsquo;entrée 2. Pour le moment, ce pointeur ne contient que des zéros, vu que l&rsquo;exécution n&rsquo;a pas encore commencée. Plaçons un breakpoint sur l&rsquo;appel à <code>strcmp()</code>, et examinons la valeur du pointeur.</p>
<pre>(gdb) b *0x0804844f         //Correspond au call   0x8048364 &lt;strcmp@plt&gt;
Breakpoint 1 at 0x804844f

(gdb) r
Starting program: /media/echange/Hacking/Reversing_Linux/plt_got/bin_str

Breakpoint 1, 0x0804844f in main ()
Current language:  auto; currently asm

(gdb) x 0x8049664
0x8049664 &lt;_GLOBAL_OFFSET_TABLE_+8&gt;:    0xb7f12c40</pre>
<p>On constate qu&rsquo;une fois le programme lancé, la valeur du pointeur a été modifiée. C&rsquo;est la procédure de lancement de l&rsquo;exécutable qui a en fait initialisé cette valeur. Où pointe-t-elle ?</p>
<pre>(gdb) info files
...
0x08049658 - 0x0804965c is .got
0x0804965c - 0x0804967c is .got.plt
0x0804967c - 0x08049688 is .data
0x08049688 - 0x0804968c is .bss
0xb7f000f4 - 0xb7f001b4 is .hash in /lib/ld-linux.so.2
0xb7f001b4 - 0xb7f00298 is .gnu.hash in /lib/ld-linux.so.2
0xb7f00298 - 0xb7f00468 is .dynsym in /lib/ld-linux.so.2
0xb7f00468 - 0xb7f005fc is .dynstr in /lib/ld-linux.so.2
0xb7f005fc - 0xb7f00636 is .gnu.version in /lib/ld-linux.so.2
0xb7f00638 - 0xb7f00700 is .gnu.version_d in /lib/ld-linux.so.2
0xb7f00700 - 0xb7f00760 is .rel.dyn in /lib/ld-linux.so.2
0xb7f00760 - 0xb7f00788 is .rel.plt in /lib/ld-linux.so.2
0xb7f00788 - 0xb7f007e8 is .plt in /lib/ld-linux.so.2
0xb7f007f0 - 0xb7f157af is .text in /lib/ld-linux.so.2  &lt;=====
0xb7f157b0 - 0xb7f158e1 is __libc_freeres_fn in /lib/ld-linux.so.2
0xb7f15900 - 0xb7f19320 is .rodata in /lib/ld-linux.so.2
0xb7f19320 - 0xb7f1941c is .eh_frame_hdr in /lib/ld-linux.so.2
0xb7f1941c - 0xb7f19850 is .eh_frame in /lib/ld-linux.so.2
0xb7f1acc0 - 0xb7f1af28 is .data.rel.ro in /lib/ld-linux.so.2
0xb7f1af28 - 0xb7f1afe0 is .dynamic in /lib/ld-linux.so.2
0xb7f1afe0 - 0xb7f1afe8 is .got in /lib/ld-linux.so.2
0xb7f1aff4 - 0xb7f1b014 is .got.plt in /lib/ld-linux.so.2
0xb7f1b020 - 0xb7f1b5b0 is .data in /lib/ld-linux.so.2
0xb7f1b5b0 - 0xb7f1b5b4 is __libc_subfreeres in /lib/ld-linux.so.2
0xb7f1b5b4 - 0xb7f1b668 is .bss in /lib/ld-linux.so.2
0xb7d9c174 - 0xb7d9c194 is .note.ABI-tag in /lib/tls/i686/cmov/libc.so.6
0xb7d9c194 - 0xb7d9fcbc is .gnu.hash in /lib/tls/i686/cmov/libc.so.6
0xb7d9fcbc - 0xb7da8a8c is .dynsym in /lib/tls/i686/cmov/libc.so.6
0xb7da8a8c - 0xb7dae274 is .dynstr in /lib/tls/i686/cmov/libc.so.6
...</pre>
<p>La région contenant cette adresse d&rsquo;est rien d&rsquo;autre que la .text du linker dynamique, ld.so ! On se trouve dans la fonction du linker permettant d&rsquo;appeler la véritable fonction <code>strcmp()</code>.</p>
<h3>La résolution des symboles</h3>
<p>Mais comment le linker sait-il qu&rsquo;il faut appeler <code>strcmp()</code> et pas une autre fonction ? Tout simplement grâce au push 0&#215;20 précédent : 0&#215;20 correspond en fait à un offset correspondant ici à <code>strcmp()</code>. Les instructions de la PLT l&rsquo;ont empilé afin de le passer en paramètre à la fonction du linker.</p>
<p>Désassemblons cette fonction :</p>
<pre>(gdb) disas 0xb7ff6c40 0xb7ff6c40+28
//Le +28 a été trouvé en tatonnant jusqu'à trouver un ret
Dump of assembler code from 0xb7f12c40 to 0xb7f12c5c:
0xb7f12c40:     push   %eax
0xb7f12c41:     push   %ecx
0xb7f12c42:     push   %edx
0xb7f12c43:     mov    0x10(%esp),%edx
0xb7f12c47:     mov    0xc(%esp),%eax
0xb7f12c4b:     call   0xb7f0d350
0xb7f12c50:     pop    %edx
0xb7f12c51:     mov    (%esp),%ecx
0xb7f12c54:     mov    %eax,(%esp)
0xb7f12c57:     mov    0x4(%esp),%eax
0xb7f12c5b:     ret    $0xc
End of assembler dump.</pre>
<p>Cette fonction est relativement courte ; elle ne fait en réalité qu&rsquo;appeler la véritable fonction de résolution des adresses. Lorsqu&rsquo;elle est appelée, le sommet de pile contient une adresse (correspondant à GOT[1], qui a été pushée à l&rsquo;entrée 0 de la PLT), et juste en dessous se trouve l&rsquo;index de <code>strcmp</code>, soit 0&#215;20. Vérifions en plaçant un breakpoint au début et en examinant la pile :</p>
<pre>(gdb) b *0xb7f12c40
Breakpoint 2 at 0xb7f12c40

(gdb) c
Continuing.

Breakpoint 2, 0xb7f12c40 in ?? () from /lib/ld-linux.so.2

(gdb) x/2x $esp
0xbfbf50b4:     0xb7f1b668      0x00000020</pre>
<p>Vérifions que le 1er pointeur correspond bien à l&rsquo;entrée 1 de la GOT :</p>
<pre>(gdb) x 0x8049660
0x8049660 &lt;_GLOBAL_OFFSET_TABLE_+4&gt;:    0xb7f1b668</pre>
<p>Bingo ! On a bien en sommet de pile l&rsquo;adresse contenue dans GOT[1] et juste en dessous l&rsquo;index de strcmp(), 0&#215;20.</p>
<p>Revenons au code de cette fonction.</p>
<pre>(gdb) disas 0xb7f12c40 0xb7f12c40+28
Dump of assembler code from 0xb7f12c40 to 0xb7f12c5c:
0xb7f12c40:     push   %eax
0xb7f12c41:     push   %ecx
0xb7f12c42:     push   %edx
0xb7f12c43:     mov    0x10(%esp),%edx
0xb7f12c47:     mov    0xc(%esp),%eax
0xb7f12c4b:     call   0xb7f0d350
0xb7f12c50:     pop    %edx
0xb7f12c51:     mov    (%esp),%ecx
0xb7f12c54:     mov    %eax,(%esp)
0xb7f12c57:     mov    0x4(%esp),%eax
0xb7f12c5b:     ret    $0xc
End of assembler dump.</pre>
<p>Elle commence par 3 push, permettant de sauvegarder des registres. Ainsi, nos deux valeurs en sommet de pile vont être décalées de 3*4 = 12 octets. Juste après ces 3 <code>push</code>, on a deux <code>mov</code>. Le premier place dans %edx une valeur située sur la pile à l&rsquo;offset 0&#215;10 soit 16 = 4 * 4 octets. Il s&rsquo;agit donc de l&rsquo;index de <code>strcmp()</code>, 0&#215;20. Le second place dans %eax la valeur suivante, soit celle de GOT[1]. Puis un appel de fonction a lieu.</p>
<p>On arrive alors dans une fonction relativement complexe, qui se situe toujours dans la section .text de ld.so. C&rsquo;est elle qui est chargée d&rsquo;effectuer la résolution des symbolesen recherchant dans les librairies. Si vous essayez de la désassembler, vous constaterez qu&rsquo;elle est relativement longue et complexe. Comme ici le but n&rsquo;est pas d&rsquo;être exhaustif, je ne la détaillerai pas. En plus, je n&rsquo;ai pas encore eu le courage de la reverser dans sesmoindres détails&#8230;</p>
<p>Continuons donc. Plaçons un breakpoint juste après le call de cette fonction, en 0xb7f12c50.</p>
<pre>(gdb) b *0xb7f12c50
Breakpoint 3 at 0xb7f12c50

(gdb) c
Continuing.

Breakpoint 3, 0xb7f12c50 in ?? () from /lib/ld-linux.so.2</pre>
<p>Les instructions suivantes manipulent des registres. Dans le cadre de cet article, seules deux instructions sont intressantes :</p>
<pre>0xb7f12c54:     mov    %eax,(%esp)
et
0xb7f12c5b:     ret    $0xc</pre>
<p>Le mov place la valeur de %eax en sommet de pile, tandis que le ret ordonne au CPU de continuer l&rsquo;exécution du code à l&rsquo;adresse située sur le sommet de pile. Autrement dit, juste après le call de la fonction de résolution des symboles, on saute sur l&rsquo;adresse contenue dans %eax ! Regardons ce que vaut ce registre&#8230;</p>
<pre>(gdb) info registers
eax            0xb7e0dd20       -1210000096
ecx            0x0      0
edx            0x8049678        134518392
ebx            0xb7ee6ff4       -1209110540
esp            0xbfbf50a8       0xbfbf50a8
ebp            0xbfbf50f8       0xbfbf50f8
esi            0xb7f1ace0       -1208898336
edi            0x0      0
eip            0xb7f12c50       0xb7f12c50
...</pre>
<p>Que peut bien représenter l&rsquo;adresse 0xb7e0dd20 ?</p>
<pre>(gdb) info files
...
0xb7d9c174 - 0xb7d9c194 is .note.ABI-tag in /lib/tls/i686/cmov/libc.so.6
0xb7d9c194 - 0xb7d9fcbc is .gnu.hash in /lib/tls/i686/cmov/libc.so.6
0xb7d9fcbc - 0xb7da8a8c is .dynsym in /lib/tls/i686/cmov/libc.so.6
0xb7da8a8c - 0xb7dae274 is .dynstr in /lib/tls/i686/cmov/libc.so.6
0xb7dae274 - 0xb7daf42e is .gnu.version in /lib/tls/i686/cmov/libc.so.6
0xb7daf430 - 0xb7daf730 is .gnu.version_d in /lib/tls/i686/cmov/libc.so.6
0xb7daf730 - 0xb7daf770 is .gnu.version_r in /lib/tls/i686/cmov/libc.so.6
0xb7daf770 - 0xb7db2140 is .rel.dyn in /lib/tls/i686/cmov/libc.so.6
0xb7db2140 - 0xb7db2188 is .rel.plt in /lib/tls/i686/cmov/libc.so.6
0xb7db2188 - 0xb7db2228 is .plt in /lib/tls/i686/cmov/libc.so.6
0xb7db2230 - 0xb7eb2d84 is .text in /lib/tls/i686/cmov/libc.so.6  &lt;=====
0xb7eb2d90 - 0xb7eb3de8 is __libc_freeres_fn in /lib/tls/i686/cmov/libc.so.6
0xb7eb3df0 - 0xb7eb4082 is __libc_thread_freeres_fn in /lib/tls/i686/cmov/libc.so.6
0xb7eb40a0 - 0xb7ecf090 is .rodata in /lib/tls/i686/cmov/libc.so.6
0xb7ecf090 - 0xb7ecf0a3 is .interp in /lib/tls/i686/cmov/libc.so.6
0xb7ecf0a4 - 0xb7ed1c90 is .eh_frame_hdr in /lib/tls/i686/cmov/libc.so.6
0xb7ed1c90 - 0xb7ee1544 is .eh_frame in /lib/tls/i686/cmov/libc.so.6
0xb7ee1544 - 0xb7ee19b0 is .gcc_except_table in /lib/tls/i686/cmov/libc.so.6
0xb7ee19b0 - 0xb7ee4d28 is .hash in /lib/tls/i686/cmov/libc.so.6
0xb7ee51ec - 0xb7ee51f4 is .tdata in /lib/tls/i686/cmov/libc.so.6
...</pre>
<p>Tiens, elle se trouve dans la .text&#8230; Par hasard, ce ne serait pas l&rsquo;adresse de <code>strcmp</code> ?</p>
<pre>(gdb) p strcmp
$1 = {&lt;text variable, no debug info&gt;} 0xb7e0dd20 &lt;strcmp&gt;</pre>
<p>Eh si ! Autrement dit, la fonction de résolution des symboles a résolu correctement <code>strcmp</code> et a placé son adresse dans %eax.</p>
<h3>Et la GOT dans tout ça ?</h3>
<p>Nous venons de voir le cheminement (d&rsquo;un point de vue assez haut niveau) d&rsquo;un appel de fonction situé dans une bibliothèque partagée. Comme on a pu le constater, chaque appel de fonction entraîne à priori une résolution de symbole, ce qui paraît fastidieux. Fort heureusement, par défaut, ld.so ne résoud pas un symbole à chaque fois qu&rsquo;on tente d&rsquo;y accéder, mais uniquement la 1ère fois. Par exemple, si vous avez 10 appels à strcmp() dans un programme, le 1er appel entraînera une résolution, et l&rsquo;adresse de strcmp() sera gardée en mémoire pour les 9 appels suivant. C&rsquo;est ce que l&rsquo;on appelle l&rsquo;<em>évaluation fainéante</em> : on ne fait que le minimum d&rsquo;opération, et juste à temps.</p>
<p>Où et comment les adresses des symboles sont-elles gardées en mémoire une fois résolues ? Réponse : dans la GOT ! Pour le comprendre, relançons le programme et plaçons un breakpoint dans l&rsquo;entrée de la PLT correspondant à <code>strcmp</code>.</p>
<pre>$ gdb ./bin_str

(gdb) disas 0x08048314 0x08048374   //Les adresses de la PLT, qui restent fixes
Dump of assembler code from 0x8048314 to 0x8048374:
0x08048314 &lt;_init+48&gt;:  pushl  0x8049660
0x0804831a &lt;_init+54&gt;:  jmp    *0x8049664
0x08048320 &lt;_init+60&gt;:  add    %al,(%eax)
0x08048322 &lt;_init+62&gt;:  add    %al,(%eax)
0x08048324 &lt;__gmon_start__@plt+0&gt;:      jmp    *0x8049668
0x0804832a &lt;__gmon_start__@plt+6&gt;:      push   $0x0
0x0804832f &lt;__gmon_start__@plt+11&gt;:     jmp    0x8048314 &lt;_init+48&gt;
0x08048334 &lt;__libc_start_main@plt+0&gt;:   jmp    *0x804966c
0x0804833a &lt;__libc_start_main@plt+6&gt;:   push   $0x8
0x0804833f &lt;__libc_start_main@plt+11&gt;:  jmp    0x8048314 &lt;_init+48&gt;
0x08048344 &lt;printf@plt+0&gt;:      jmp    *0x8049670
0x0804834a &lt;printf@plt+6&gt;:      push   $0x10
0x0804834f &lt;printf@plt+11&gt;:     jmp    0x8048314 &lt;_init+48&gt;
0x08048354 &lt;__stack_chk_fail@plt+0&gt;:    jmp    *0x8049674
0x0804835a &lt;__stack_chk_fail@plt+6&gt;:    push   $0x18
0x0804835f &lt;__stack_chk_fail@plt+11&gt;:   jmp    0x8048314 &lt;_init+48&gt;
0x08048364 &lt;strcmp@plt+0&gt;:      jmp    *0x8049678
0x0804836a &lt;strcmp@plt+6&gt;:      push   $0x20
0x0804836f &lt;strcmp@plt+11&gt;:     jmp    0x8048314 &lt;_init+48&gt;
End of assembler dump.

(gdb) b *0x08048364
Breakpoint 1 at 0x8048364

(gdb) r
Starting program: /media/echange/Hacking/Reversing_Linux/plt_got/bin_str

Breakpoint 1, 0x08048364 in strcmp@plt ()
Current language:  auto; currently asm

(gdb) x 0x8049678
0x8049678 &lt;_GLOBAL_OFFSET_TABLE_+28&gt;:   0x0804836a</pre>
<p>L&rsquo;entrée correspondante de la GOT contient toujours l&rsquo;adresse de l&rsquo;instruction suivante dans la PLT. Quel intérêt ? A ce moment, il faut noter que le symbole <code>strcmp</code> n&rsquo;est pas encore résolu, donc il est normal que cette entrée ne comporte aucune valeur intéressante. Plaçons un watchpoint sur cette entrée de la GOT afin de voir si elle change au fil du temps.</p>
<pre>(gdb) watch *0x8049678
Hardware watchpoint 2: *134518392

(gdb) c
Continuing.
Hardware watchpoint 2: *134518392

Old value = 134513514
New value = -1209639648
0xb7f6545d in ?? () from /lib/ld-linux.so.2</pre>
<p>Apparamment, la valeur de l&rsquo;entrée a changé ! Observons sa nouvelle valeur :</p>
<pre>(gdb) x 0x8049678
0x8049678 &lt;_GLOBAL_OFFSET_TABLE_+28&gt;:   0xb7e65d20

(gdb) p strcmp
$1 = {&lt;text variable, no debug info&gt;} 0xb7e65d20 &lt;strcmp&gt;</pre>
<p>Ainsi on s&rsquo;aperçoit qu&rsquo;elle correspond désormais à l&rsquo;adresse de <code>strcmp()</code>. Désormais, si le programme souhaîte faire d&rsquo;autres appels à <code>strcmp()</code>, il n&rsquo;aura plus à effectuer la résolution de symbole puisque le jmp situé dans l&rsquo;entrée de la PLT référence directement l&rsquo;adresse de <code>strcmp()</code> !</p>
<p>Quand cette valeur a-t-elle été écrite ? Pour cela, il suffit de regarder <code>%eip</code> et de voir dans quelle zone nous sommes.</p>
<pre>(gdb) info registers
eax            0xb7e65d20       -1209639648
ecx            0x0      0
edx            0x8049678        134518392
ebx            0xb7f72ff4       -1208537100
esp            0xbfae4f58       0xbfae4f58
ebp            0xbfae4f90       0xbfae4f90
esi            0xb7f56858       -1208653736
edi            0xb7f73668       -1208535448
eip            0xb7f6545d       0xb7f6545d
eflags         0x246    [ PF ZF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

(gdb) disas $eip-3 $eip+20
Dump of assembler code from 0xb7f6545a to 0xb7f65471:
0xb7f6545a:     mov    %eax,(%edx,%ecx,1)
0xb7f6545d:     lea    -0xc(%ebp),%esp
0xb7f65460:     pop    %ebx
0xb7f65461:     pop    %esi
0xb7f65462:     pop    %edi
0xb7f65463:     pop    %ebp
0xb7f65464:     ret
0xb7f65465:     xor    %edx,%edx
0xb7f65467:     jmp    0xb7f653de
0xb7f6546c:     lea    -0x2477(%ebx),%eax
...</pre>
<p>En cherchant un peu, on remarque qu&rsquo;on se trouve juste à la fin de la fonction de résolution des symboles. L&rsquo;instruction responsable de l&rsquo;écriture de l&rsquo;adresse dans la GOT est le <code>mov</code> %eax,(%edx,%ecx,1). En inspectant les registres, on voit que cela correspond bien à l&rsquo;opération *0&#215;8049678 = 0xb7e65d20 (l&rsquo;adresse de <code>strcmp</code>).</p>
<p>On notera que cette méthode est comparable aux systèmes de caches des processeurs : l&rsquo;objectif est d&rsquo;accelérer les accès futurs aux fonctions en accédant une fois pour toute à une donnée et en la plaçant dans une zone d&rsquo;accès plus rapide. Pour information, il est tout à fait possible de désactiver ce système de mise en cache en utilisant des variables d&rsquo;environnement reconnues par ld.so. Par exemple, la variable d&rsquo;environnement <code>LD_BIND_NOT</code>, si elle est définie, permet ainsi de désactiver complétement l&rsquo;écriture des adresses dans la GOT, tandis que la variable <code>LD_BIND_NOW</code> indique à ld.so d&rsquo;effectuer toutes les résolutions dès le début, donc de remplir la GOT dès le lancement de l&rsquo;exécutable. Pour plus de renseignements, consultez le man de ld.so.</p>
<h3>Récapitulatif</h3>
<p>Le schéma suivant retrace le fil de l&rsquo;exécution lors d&rsquo;un appel à <code>strcmp()</code>, et récapitule les différents liens entre la PLT et la GOT.</p>
<h4>1er appel de strcmp : symbole non encore résolu</h4>
<pre>main:
...
call   0x8048364 &lt;strcmp@plt&gt; ----+
mov    %eax,-0x1c(%ebp)           |
...                               |
                                  |
                                  |
0x8048364 (Entrée de la PLT) : &lt;--+
jmp    *0x8049678  -----------------------&gt; 0x8049678 (Entrée de la GOT) :
push   $0x20  &lt;------------------------------ 0x0804836a
jmp    0x8048314 &lt;_init+48&gt; --------+
                                    |
                                    |
0x8048314 (Entrée 0 de la PLT) : &lt;--+
pushl  0x8049660
jmp    *0x8049664  -----------------------&gt; 0x8049664 (Header de la GOT) :
                                              0xb7f12c40 --+
                                                           |
                                                           |
0xb7f12c40 (.text de ld.so, résolution + appel) : &lt;--------+
push   %eax              // Sauvegarde de registres
push   %ecx
push   %edx
mov    0x10(%esp),%edx   // Récupération du code de la fonction strcmp (0x20)
mov    0xc(%esp),%eax
call   0xb7f4c350  -------&gt; Appel de la fonction de résolution des symboles
                            L'adresse du symbole (strcmp) est placé dans %eax.
                            L'entrée de la .got est patchée avec cette adresse.
... &lt;---------------------- Retour de la fonction
mov    %eax,(%esp)       // L'adresse de strcmp (0xb7e65d20) est empilée
...
ret    $0xc ------------------------------+ // On saute sur strcmp
                                          |
                                          |
0xb7e65d20 (strcmp, .text de la libc): &lt;--+
...
ret                      // Retour au main</pre>
<h4>2ème appel de strcmp : symbole déjà résolu</h4>
<pre>main:
...
call   0x8048364 &lt;strcmp@plt&gt;  ---+
mov    %eax,-0x1c(%ebp)           |
...                               |
                                  |
                                  |
0x8048364 (Entrée de la .plt) : &lt;-+
jmp    *0x8049678  ----------------&gt; 0x8049678 (Entrée de la GOT) :
                                       0xb7e65d20 --+ // Symbole résolu !
push   $0x20                                        | // Les instructions
jmp    0x8048314 &lt;_init+48&gt;                         | // suivantes ne sont pas
                                                    | // exécutées.
                                                    |
0xb7e65d20 (strcmp, .text de la libc): &lt;------------+
...
ret                      // Retour au main</pre>
<h3>Références</h3>
<p>Comme le précise l&rsquo;introduction, je n&rsquo;avais pas Internet quand j&rsquo;ai réalisé cet article ; je ne peux donc pas citer de page Web. Je me suis beaucoup aidé de la documentation off-line des programmes que j&rsquo;avais sous la main, à savoir :</p>
<ul>
<li>Le man de ld.so (man <code>ld.so</code>)</li>
<li>L&rsquo;aide de gdb (commande <code>help</code> de gdb)</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://www.segmentationfault.fr/linux/role-plt-got-ld-so/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
	</channel>
</rss>
