Callgate

novembre 2nd, 2007 at 04:41 admin

Retour aux sources avec aujourd’hui du bon assembleur x86 et de la prog système. Je voudrais vous présenter un mécanisme peu utilisé disponible sur l’architecture 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 Descriptor Table) sous Windows.

Toujours dans l’optique de corrompre un système, il peut être intéressant quand on est administrateur d’avoir la possibilité d’exécuter du code avec les privilèges kernel. Dans le cas ou on n’a pas envie de coder un driver pour modifier la SSDT/IDT/GDT ou toute autre structures kernel il faut jouer avec l’architecture x86.

Le CPL (Current Privilege Level) d’un thread est indiqué par ses segments, il y en a 6, CS, DS, ES, FS, GS et SS. CS est le segment de code, (celui fait référence à la partie de la mémoire contenant le code à être exécuté), SS est le segment de stack, DS, ES, FS et GS sont des segments de données. Sous Windows GS n’est pas utilisé et FS représente une zone mémoire contenant des structures importantes (TEB en user-land KPCR en kernel-land). Chaque segment selector possède la forme suivante :

Segment selector

Le RPL (Required Privilege Level) indique à quel niveau de privilège doit fonctionner le segment, le champ TI (Table Indicitor) nous indique si on doit lire le Segment Descriptor dans la LDT (Local Descriptor Table) ou la GDT. Justement en parlant de Segment Descriptor, ceux-ci sont référencés à travers le champ Index du segment. L’Index correspond à l’indice du Segment Descriptor dans la GDT qui se situe bien cachée dans le kernel-land. Pour mieux comprendre prenons les valeurs des segments user-land :

userland segments :
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000

0x1B=  11 0 11
0x23= 100 0 11
0x3B= 111 0 11

Pour les segments userland le RPL est à 3, normal c’est du ring3. Ensuite le champ Index nous donne les indices dans la GDT des Segments Descriptor. La structure d’un Segment Descriptor est la suivante :

Segment descriptor

Pour mieux comprendre prennons un exemple. Le segment CS userland indique l’indice 3 dans la GDT.

kd> !ProtMode.Descriptor GDT 3
------------------- 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

On voit bien qu’on a un Segment Descriptor de code possédant un DPL (Descriptor Privilege Level) de 3.

Concernant les segments kernel

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

Le segment selector CS kernel nous indique l’indice 1 dans la GDT.

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 = 0x000fffff

Nickel, un code Segment Decriptor avec un DPL de 0.

Maintenant sachant tout cela, on va pouvoir rentrer dans le vif du sujet. Pour passer du ring3 ou ring0, l’architecture x86 ne laisse pas 36000 façons de possible. Actuellement, le moyen le plus utilisé pour réaliser cette opération est de faire appel à l’instruction SYSENTER, je vous laisser lire le post SYSENTER, stepping into da ring0. Il existe d’autres façons de passer en ring0, une méthode que je trouve assez peu connu est celle de la callgate.

En théorie la callgate, ce n’est pas trop difficile à comprendre. Il faut définir 3 choses :
- Le segment de code auquel on veut accéder.
- La fonction qui sera exécuté.
- Le niveau de privilège requit par l’appelant pour pourvoir utilisé la fonction.

Plus concrètement une callgate s’ajoute soit la GDT, soit dans la LDT, c’est une structure qui ressemble beaucoup à celle d’un segment descriptor, sauf que c’est un callgate descriptor (hewi!) :

Gate descriptor

Lors de l’appel, on effectue le parcourt suivant :

Callgate

C’est bien joli, tout ca, mais, comment ajoute t’on une callgate ? Evidemment, (sinon ca serait drôle) il faut être admin sur la machine et disposé du SeDebugPrivilege. Dans son article de phrack 59, CrazyLord utilisait le \Device\PhysicalMemory pour ajouter une callgate dans la GDT kernel. Perso, je n’aime pas trop cette méthode que je trouve un peu « crade », du fait qu’on manipule des adresse physiques et non virtuelles, je préfère utiliser l’API native NtSystemDebugControl avec les ControlCode DebugSysReadVirtual et DebugSysWriteVirtual qui permet de lire et d’écrire dans le kernel depuis le user-land de manière plus simple.

Pour retrouver l’emplacement de la GDT dans le kernel, on utilise l’instruction SGDT qui nous renvoie dans l’adresse indiqué une structure KGDTENTRY de la forme :

typedef struct _KGDTENTRY {
   WORD LimitLow; // size in bytes of the GDT
   WORD BaseLow;  // address of GDT (low part)
   WORD BaseHigh; // address of GDT (high part)
} KGDTENTRY, *PKGDTENTRY;

Après il suffit de faire :

KGDTENTRY GDT;
PVOID GDTBase;
[...]
__asm
{
	//sgdt
	//Stores the global descriptor table register (GDTR) into the destination operand. In legacy and
	//compatibility mode, the destination operand is 6 bytes; in 64-bit mode, it is 10 bytes.
	lea ecx, GDT
	sgdt fword ptr [ecx]
}

GDTBase=(PVOID)((GDT.BaseLow)|((ULONG)GDT.BaseHigh<<16));

On obtient ainsi la taille de la GDT et son emplacement mémoire. Après on va recopier la GDT dans un buffer userland et on va rechercher un segment descriptor dont le flag P (segment-present) est à 0, ce qui veut dire que cette entrée de la GDT est non utilisée et qu’on peut y installer notre callgate sans problème.

Il faut aussi savoir qu’il existe une GDT pour chaque core, si on veut installer une callgate on peut soit :
- Choisir une GDT précise et faire en sorte que le thread tourne sur le bon core au moment de l’appel à la callgate avec l’API SetThreadAffinityMask.

- Ne pas s’embêter et modifier toutes les GDT. Celles-ci étant normalement égales, on devrait modifier la même entrée de la GDT. Ainsi on n’aura pas à se préoccuper sur quel core s’exécutera le thread.

J’ai choisit de ne pas m’embêter et donc de modifier toutes les GDT. Maintenant, qu’on sait ou écrire dans la GDT, choisissons les valeurs de notre callgate descriptor.

En fait la callgate contient le segment selector qui référence le segment descriptor sous lequel notre code sera exécuté. Nous comme on veut tourner en ring0 on remplit se champ avec la même valeur que celle du segment CS ring0, c’est à dire 8. Sinon pour le reste c’est assez évident :

MyCallGate.offset_0_15=(WORD)((ULONG)Ring0Func&0xFFFF);
MyCallGate.selector=8; //ring0 CS descriptor
MyCallGate.param_count=0; //no parameters
MyCallGate.some_bits=0;
MyCallGate.type=12;     //32-bits callgate (• The 32-bit call gate (0Ch), which is redefined as the 64-bit call gate.)
MyCallGate.app_system=0; //system segment
MyCallGate.dpl=3;      //ring 3 code can call
MyCallGate.present=1;
MyCallGate.offset_16_31=(WORD)((ULONG)Ring0Func>>16);

Reste à savoir comment on va appeler la callgate. Dans le jeu d’instruction IA-32 il existe l’instruction Call, ok ca tout le monde connait, mais l’instruction call possède une feature moins connu qu’est le far call. Un far call est un call sur une procédure qui est situé dans un segment différent. Hop petit extrait des man intels.
If the selected descriptor is for a code segment, a far call to a code segment at the
same privilege level is performed. (If the selected code segment is at a different priv
ilege level and the code segment is non-conforming, a general-protection exception
is generated.) A far call to the same privilege level in protected mode is very similar
to one carried out in real-address or virtual-8086 mode. The target operand specifies
an absolute far address either directly with a pointer (ptr16:16 or ptr16:32) or indi-
rectly with a memory location (m16:16 or m16:32). The operand- size attribute
determines the size of the offset (16 or 32 bits) in the far address. The new code
segment selector and its descriptor are loaded into CS register; the offset from the
instruction is loaded into the EIP register.
A call gate (described in the next paragraph) can also be used to perform a far call to
a code segment at the same privilege level. Using this mechanism provides an extra
level of indirection and is the preferred method of making calls between 16-bit and
32-bit code segments.
When executing an inter-privilege-level far call, the code segment for the procedure
being called must be accessed through a call gate. The segment selector specified by
the target operand identifies the call gate. The target operand can specify the call
gate segment selector either directly with a pointer (ptr16:16 or ptr16:32) or indi-
rectly with a memory location (m16:16 or m16:32). The processor obtains the
segment selector for the new code segment and the new instruction pointer (offset)
from the call gate descriptor. (The offset from the target operand is ignored when a
call gate is used.)

Ce qu’il faut retenir c’est qu’un far call peut se définir de la façon suivante :

typedef struct _FARCALL {
	DWORD Offset;
	WORD SegSelector;
} FARCALL, *PFARCALL;

Que lors d’un far call vers une callgate seule le champ SegSelector est prit en compte, normal le pointeur sur la fonction à exécuter est définit dans le callgate descriptor. Il suffit donc de faire :

FARCALL Farcall={0};
[...]
//install un new seg desc dans les GDT de chaque core et renvoie le segment permettant d'y acceder
Farcall.SegSelector=InstallCallGate();

__asm
{
	lea ecx, Farcall
	call fword ptr [ecx]
}

Pour appeler notre jolie callgate, wOOooTz !
Pour l’exemple j’ai prit la fonction Ring0Func suivante :

void __declspec(naked) Ring0Func() {
// ring0 prolog
__asm
{
	pushad // push eax,ecx,edx,ebx,ebp,esp,esi,edi onto the stack
	pushfd // decrement stack pointer by 4 and push EFLAGS onto the stack
	cli    // disable interrupts
}
// execute your ring0 code here ...

__asm{int 3} // HEQDSHOT!

// ring0 epilog
__asm
{
	sti   // restore interrupts
	popfd // restore registers pushed by pushfd
	popad // restore registers pushed by pushad
	retf  // you may retf  if you pass arguments
}
}

Je lance mon programme et là !

Break instruction exception - code 80000003 (first chance)
0040081a cc              int     3
kd> r
eax=00000120 ebx=7ffd5000 ecx=0012ff44 edx=00000001 esi=002d0031 edi=00370031
eip=0040081a esp=f6ec7dac ebp=0012ff4c iopl=0         nv up di pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000046
0040081a cc              int     3

HEQSHOTZ ! Ma callgate à bien été appelé avec un CS de 8 (ring0) wOOotz!

Dernière chose aussi, les autres segments ont aussi été mis à jour pour être des segments kernel. Pour le segment de stack (SS) c’est indiqué par les man intels :
Stack Switching. The processor performs an automatic stack switch when a control transfer causes a change in privilege levels to occur. Switching stacks isolates more-privileged software stacks from less-privileged software stacks and provides a mechanism for saving the return pointer back to the program that initiated the call.

Donc je n’arrive pas à comprendre pour quoi les autres segments, DS, ES, FS et GS sont modifiés, si quelqu’un a une idée qu’il laisse un comment.

Au final, les callgate, c’est bien cool si on veut balancer du code ring0 sans charger de driver. Reste que je n’ai pas tout exposé dans ce post, je vous invite à aller lire les man intels sur le sujet pour mieux comprendre le fonctionnement, en tout cas ce post est une bonne intro au sujet. J’espère que ca vous à plus.

Hop le petit code de démonstration :

http://ivanlef0u.fr/repo/Callgate.rar

Sinon au passage petite pub pour la dernière rlz de mindkind (no comments!) :
http://lastcall.mindkind.org/mindkind1011.zip

Dédicace à une tapz :]
<tapz> je te met au defis de place dans ton post le mot « langouste »

refs :

Call gate
http://en.wikipedia.org/wiki/Call_gate

Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A System Programming Guide

http://www.intel.com/design/processor/manuals/253668.pdf

4.8.3 Call Gates

AMD64 Architecture Programmer’s Manual Volume 2 System Programming
http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/24593.pdf
4.11.2 Control Transfers Through Call Gates

AMD64 Architecture Programmer’s Manual Volume 3 General-Purpose and System Instructions.pdf
http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/24594.pdf
Call (Far)

From Russia with Rootkit
http://www.f-secure.com/weblog/archives/00000838.html

Segmentation Fault
http://www.ivanlef0u.tuxfamily.org/?p=46

GDT / LDT
http://uninformed.org/index.cgi?v=8&a=2&p=9

Adding New Software Interrupts
http://www.windowsitlibrary.com/Content/356/10/1.html

Entry Filed under: RE

12 Comments

  • 1. Taron  |  novembre 2nd, 2007 at 16:57

    http://www.f-secure.com/weblog/archives/kasslin_AVAR2006_KernelMalware_paper.pdf


  • 2. admin  |  novembre 2nd, 2007 at 17:05

    Pas mal, j’avais pas vu … moi au moins je fournit un code d’exemple ;)


  • 3. Taron  |  novembre 3rd, 2007 at 00:13

    Ouais c’est pas une nouvelle méthode mais je trouve que tu l’illustres très bien… en plus avec un code ui..


  • 4. mxatone  |  novembre 3rd, 2007 at 11:21

    Très interessant et bien expliqué.

    Si vous souhaitez plus de détails :
    http://www.securiteam.com/windowsntfocus/5TP0B2KC0K.html

    A noter que Microsoft connait ce problème et n’as pas souhaité le corrigé car il faut le privilege debug, en gros si vous avez le privilege debug, pour Microsoft vous avez le droit de tout faire. (Meme si y a un priviliege chargement de driver, c pas grave :p)


  • 5. Taron  |  novembre 3rd, 2007 at 13:01

    Ouais c’est vrai que ça craint un peu.. c’est une porte ouverte aux rootkits.


  • 6. b0l0k  |  novembre 3rd, 2007 at 17:02

    Hmm, je croyais que t’avais deja fais un article sur les callgates :D

    En tout cas, bon article :)


  • 7. whiskas  |  novembre 5th, 2007 at 16:58

    « Sous Windows GS n’est pas utilisé »
    Sous windows xp x64 il me semble que si, en fait le fs: reste pour la retro-compatibilite avec les app en 32bits, et donc contient le TEB en 32bits, alors que gs: pointe vers un TEB version 64bits.

    A confirmer, je suis prive de windows. :\


  • 8. admin  |  novembre 6th, 2007 at 00:26

    « Recall that the gs segment register refers to the base address of the TEB on x64. »
    :] from http://www.nynaeve.net/?p=185


  • 9. martin  |  novembre 8th, 2007 at 07:29

    On peut faire un far call sur un descripteur de tss et ca fait un chgt de tache automatique(sauvegarde/restauration de tous
    les registres) avec une seule instruction. Ca a ete declare trop couteux par Jolitz il y a 20ans avec le premier port bsd sur pc.
    donc la technique n’est plus utilisee a tort a mon avis.
    Je pense que tu as utilise un emulateur pour le test?
    Si oui il y a un petit bug et sur un descripteur type 12 fait un TaskSwitch() au lieu d’un simple Callgate() inter segment?
    Sinon ?


  • 10. Babewn  |  novembre 17th, 2007 at 18:01

    Moi j’aime le colgate
    avec ca j’ai la bouche fraiche pendant 24h


  • 11. YoLeJedi  |  décembre 30th, 2007 at 22:33

    Salut,

    Sympa ton article Ivan. :)

    Je voulais juste parler d’un problème non négligeable avec les callgate sous Windows.

    Le système de callgate est effectivement un service proposé par l’architecture des x86 mais Windows de les tolère pas !

    En effet, Windows permet un passage du Ring3 à Ring0 sous certaines conditions. Et en particulier, il faut que lors du passage de Ring3 à Ring0 les interruptions soient masquées.

    Ceci est bien effectué automatiquement par le processeur en utilisant une interruption ou l’instruction sysenter mais pas en utilisant une callgate.

    Il faut donc masquer les interruptions soit même avec l’instruction cli et les démasquer avec l’instruction sti (ce que tu fais). Mais cela ne suffit pas car il y a toujours un risque que le thread en cours soit switché avant le masquage des interruptions (avant l’exécution de cli).
    Et lorsque cela arrive, c’est le crash.

    Donc, en fait, la technique marche mais il y a toujours un risque de BSOD.

    Il y a quelques années, je m’étais pris la tête avec cette histoire de callgate et je n’ai jamais trouvé de solution. Pour que cela soit fiable à 100%, il faudrait masquer les interruptions avant d’appeler la callgate. Donc il faudrait masquer les interruptions à partir du Ring3. Et là, je n’ai pas de solution.

    Pour vérifier cela, il suffit de faire une petite boucle qui appelle la callgate infiniment. Ça devrait s’arrêter sur un beau BSOD dans un temps plus ou moins long.

    Voilà pour la petite histoire :)

    Bonne fêtes,
    Lionel


  • 12. Ivanlef0u’s Blog &r&hellip  |  septembre 20th, 2008 at 14:48

    [...] TSS se fait en accédant avec un FAR JMP ou FAR CALL sur une task gate (équivalent d’une callgate pour une tâche) pouvant se situé aussi bien dans la GDT que la [...]


Trackback this post


Calendar

mars 2024
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts