Segmentation Fault

mai 5th, 2007 at 09:15 admin

Par manque d’inspiration le contenu de ce post racontera la manière dont je me suis prit une grosse baffe dans ma gueule en voulant jouer avec mon OS. Ma péripétie commence à cause d’une simple interrogation, le genre de choses qu’on se demande uniquement sous la douche le matin, après avoir absorbé sa dose vitale journalière de chocapics, des questions auxquelles seul un manuel Intel peut répondre mais qu’on à peur d’ouvrir tellement ce genre de ebooks fait de la peur. Le trip est simple, pourquoi un code userland ne peut-il lire à une adresse supérieur à 0x7FFFFFFF ? Ok vous allez me dire que c’est là qu’est chargé le kernel et ses copines, mais le vrai problème est de savoir par quel méchanisme magique le système nous balance une exception 0xC0000005 (STATUS_ACCESS_VIOLATION) dans la face.


Je ne pars pas complètement apwal, je sais que le passage de ring3 en ring0 se fait à travers l’instruction sysenter. De plus dans un précédent post (SYSENTER, stepping into da ring0) j’avais décrit son fonctionnement et notamment après avoir modifié le MSR_SYSENTER_EIP avec l’API NtSystemDebugControl j’ai pu exécuter du code qui avait accès à toute la mémoire. La clé est donc dans le fonctionnement de sysenter !

Après m’être rappelé que sysenter modifiait l’EIP mais aussi les segments CS et SS, je me suis dit que ceux-ci devaient avoir quelque chose d’intéressant à m’apprendre. Hop je prend un thread userland au hasard et je regarde ses segments.

userland segments

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000
0x1B=  11 0 11b

0x23= 100 0 11b

0x3B= 111 0 11b

En même temps je retrouve la structure décrivant un segment.

typedef struct _X86_SELECTOR

    {

    union

        {

        struct

            {

            WORD wValue;            // packed value

            WORD wReserved;

            };

        struct

            {

            unsigned RPL      :  2; // requested privilege level

            unsigned TI       :  1; // table indicator: 0=gdt, 1=ldt

            unsigned Index    : 13; // index into descriptor table

            unsigned Reserved : 16;

            };

        };

    }

    X86_SELECTOR, *PX86_SELECTOR, **PPX86_SELECTOR;

Un segment contient donc, son niveau de privilège requis, un bit désignant une table, soit la gdt ou la ldt et l’indice d’une structure X86_DESCRIPTOR dans cette table. Ici le RPL est bien à 3 (11b), le champ TI vaut 0, on est donc dans la GDT. Pour CS sont descriptor est à l’indice 3 alors que celui de SS est à l’indice 4.

Après je compare les segments avec ceux d’un thread tournant en kerneland.

kernelland segments

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000
0x08=   1 0 00b

0x10=  10 0 00b

0x23= 100 0 11b

0x30= 110 0 00b

Sysenter a fait son boulot, les segments CS et SS sont différents ainsi que FS mais ca c’est la fonction KiSystemService qui le redéfinit. Cette fois ci le descriptor de CS est à l’indice 1 dans la GDT et celui de SS à l’indice 2.

Sachant que un descriptor ressemble à :

typedef struct _X86_DESCRIPTOR

    {

    union

        {

        struct

            {

            DWORD dValueLow;        // packed value

            DWORD dValueHigh;

            };

        struct

            {

            unsigned Limit1   : 16; // bits 15..00

            unsigned Base1    : 16; // bits 15..00

            unsigned Base2    :  8; // bits 23..16

            unsigned Type     :  4; // segment type

            unsigned S        :  1; // type (0=system, 1=code/data)

            unsigned DPL      :  2; // descriptor privilege level

            unsigned P        :  1; // segment present

            unsigned Limit2   :  4; // bits 19..16

            unsigned AVL      :  1; // available to programmer

            unsigned Reserved :  1;

            unsigned DB       :  1; // 0=16-bit, 1=32-bit

            unsigned G        :  1; // granularity (1=4KB)

            unsigned Base3    :  8; // bits 31..24

            };

        };

    }

    X86_DESCRIPTOR, *PX86_DESCRIPTOR, **PPX86_DESCRIPTOR;

Après avoir vu ca, j’ai pas vraiment eu envie de dumper la GDT à la main, j’ai trouvé une extension ProtMode pour windbg qui permet d’afficher le contenu d’un X86_DESCRIPTOR de manière plus jolie.

A noter que la GDT peut etre retrouvé soit avec l’instruction sgdt, soit dans le KPCR avec la commande !pcr du KD.

kd> !ProtMode.Descriptor GDT 1

------------------- Code Segment Descriptor --------------------

GDT base = 0x8003F000, Index = 0x01, Descriptor @ 0x8003f008

8003f008 ff ff 00 00 00 9b cf 00

Segment size is in 4KB pages, 32-bit default operand and data size

Segment is present, DPL = 0, Not system segment, Code segment

Segment is not conforming, Segment is readable, Segment is accessed

Target code segment base address = 0x00000000

Target code segment size = 0x000fffffkd> !ProtMode.Descriptor GDT 2

------------------- Code Segment Descriptor --------------------

GDT base = 0x8003F000, Index = 0x02, Descriptor @ 0x8003f010

8003f010 ff ff 00 00 00 93 cf 00

Segment size is in 4KB pages, 32-bit default operand and data size

Segment is present, DPL = 0, Not system segment, Data segment

Segment is not conforming, Segment is readable, Segment is accessed

Target code segment base address = 0x00000000

Target code segment size = 0x000fffff

------------------- Code Segment Descriptor --------------------

GDT base = 0x8003F000, Index = 0x03, Descriptor @ 0x8003f018

8003f018 ff ff 00 00 00 fb cf 00

Segment size is in 4KB pages, 32-bit default operand and data size

Segment is present, DPL = 3, Not system segment, Code segment

Segment is not conforming, Segment is readable, Segment is accessed

Target code segment base address = 0x00000000

Target code segment size = 0x000fffff

kd> !ProtMode.Descriptor GDT 4

------------------- Code Segment Descriptor --------------------

GDT base = 0x8003F000, Index = 0x04, Descriptor @ 0x8003f020

8003f020 ff ff 00 00 00 f3 cf 00

Segment size is in 4KB pages, 32-bit default operand and data size

Segment is present, DPL = 3, Not system segment, Data segment

Segment is not conforming, Segment is readable, Segment is accessed

Target code segment base address = 0x00000000

Target code segment size = 0x000fffff

Comme prévu le premier descriptor est de type Code segment et possède un DPL (Descriptor Privilege Level) à 0, notre segment CS en ring0.
Le second descriptor, segment SS ring0.
Le troisième, segment CS ring3
Le quatrième, segment SS ring3.

Ici les segments sont en Flat-model, c’est à dire qu’ils décrivent entièrement l’espace mémoire (segment size*4KB).

Par contre pour le segment FS, indice 7 dans la GDT pour le userland.

kd> !ProtMode.Descriptor GDT 7

------------------- Code Segment Descriptor --------------------

GDT base = 0x8003F000, Index = 0x07, Descriptor @ 0x8003f038

8003f038 ff 0f 00 e0 fd f3 40 7f

Segment size is in bytes, 32-bit default operand and data size

Segment is present, DPL = 3, Not system segment, Data segment

Segment is not conforming, Segment is readable, Segment is accessed

Target code segment base address = 0x7ffde000

Target code segment size = 0x00000fff

L’adresse de base est 0x7ffde000 qui correspond à l’adresse du TEB du thread courant, attention depuis xp sp2 cette adresse est randomisé pour chaque threads, mais c’est pas grave avec fs:[0] on la retrouve :p

Pour un thread ring0 le segment FS vaut :

kd> !ProtMode.Descriptor GDT 6

------------------- Code Segment Descriptor --------------------

GDT base = 0x8003F000, Index = 0x06, Descriptor @ 0x8003f030

8003f030 01 00 00 f0 df 93 c0 ff

Segment size is in 4KB pages, 32-bit default operand and data size

Segment is present, DPL = 0, Not system segment, Data segment

Segment is not conforming, Segment is readable, Segment is accessed

Target code segment base address = 0xffdff000

Target code segment size = 0x00000001

En fs:[0] on retrouve l’adresse de la structure KPCR, ca peut toujours servir, notamment dans un shellcode :]

Lorsque j’essayais de lire une adresse supérieure à 0x7FFFFFFF avec un thread ring3 je me mangeais une exception car j’essayais d’accéder à une kernel page alors que mon CPL (Current Privilege Level) était à 3. La MMU génère une interruption 14 (Page Fault) qui est gérée par la routine de l’IDT KiTrap0E puis par MmAccessFault.

Je me suis dit qu’il suffisait de modif les segments à la main, comme le fait sysenter, pour avoir un thread tournant en ring0 (Il est con le Ivan parfois quand même ….). Sous Olly c’est possible, alors je lance un programme, modif ses segments pour avoir le CS et SS pareil qu’un thread ring0, je relance le thread et là c’est le drame ! Je me mange une exception. Evidemment je m’y attentais quand même un peu, ca paraissait un peu trop facile :) Par contre il me fallait comprendre pourquoi l’exception avait été générée.

Arrivé à ca point, pas le choix, faut regarder les man Intel, pénible corvée qui doit se faire en portant une combinaison NBC et des chaussettes marsupilami. Après quelques recherches, j’apprends que depuis tout petit on me ment, les segments ne font pas que 16 bits !! Il existe tout une partie cachée qui contient notamment le DPL du segment ! En fait cela permet d’éviter au processeur des cycles en plus de lecture pour lire la GDT. Ce cache ne peut être modifié directement mais des instructions comme sysenter le mettent à jour, comme le montre le manuel de l’instruction :

CS.SEL  <- SYSENTER_CS_MSR (* Operating system provides CS *)

(* Set rest of CS to a fixed value *)

CS.BASE  <- 0; (* Flat segment *)

CS.LIMIT <-  FFFFFH; (* 4-GByte limit *)

CS.ARbyte.G <-  1; (* 4-KByte granularity *)

CS.ARbyte.S  <- 1;

CS.ARbyte.TYPE  <- 1011B; (* Execute   Read, Accessed *)

CS.ARbyte.D <-  1; (* 32-bit code segment*)

CS.ARbyte.DPL <-  0;

CS.SEL.RPL <-  0;

CS.ARbyte.P <-  1;

CPL <-  0;

Donc même après avoir modifié le segment selector, le système remarquant que le RPL du segment ne correspond pas au DPL contenu dans le cache nous balance une interruption dans la face. Ouinn ….

Bref j’ai prit une baffe mais j’ai compris pourquoi :]

Références :
http://www.awprofessional.com/articles/article.asp?p=167857&rl=1

http://www.codeguru.com/cpp/w-p/system/devicedriverdevelopment/article.php/c8223__1/

http://www.summitsoftconsulting.com/SampleCode/ProtMode.zip

http://www.internals.com/articles/protmode/protmode.htm

http://www.sandpile.org/ia32/sreg.htm

Entry Filed under: RE

3 Comments

  • 1. admin  |  mai 5th, 2007 at 22:39

    J’oublie de préciser que la GDT permet de remplir le cache lors de la première utilisation du segment. Ces descriptors sont initialisés par le ntldr.


  • 2. Ivanlef0u’s Blog &r&hellip  |  novembre 2nd, 2007 at 16:41

    [...] x86 qu’est la CallGate. Avant de commencer je vous invite à lire le précédent post Segmentation Fault qui montre les concepts de la segmentation et les relations entre les segments et la GDT (Global [...]


  • 3. Ivanlef0u’s Blog &r&hellip  |  juillet 2nd, 2008 at 18:44

    [...] d’aller exécuter du code situer dans une page ring0 si votre CPL est à 3. Check this out ! Si vous voulez plus d’infos sur ce sujet allez lire le chapitre 3 “Protected-mode [...]


Trackback this post


Calendar

novembre 2019
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
252627282930  

Most Recent Posts