|
Je vais recommencer l'analyse de la routine BCHK utilisée
par Blind Read et essayer d'expliquer pourquoi un simple RET n'aurait pas du être suffisant:
* Referenced by a CALL at Address:
|:004643CA
:0046448D 33D2
xor edx, edx
:0046448F 68AB444600
push 004644AB
:00464494 64FF32
push dword ptr fs:[edx]
:00464497 892524AF4900
mov dword ptr [0049AF24], esp
:0046449D 892D28AF4900
mov dword ptr [0049AF28], ebp
:004644A3 648922
mov dword ptr fs:[edx], esp
:004644A6 EB1A
jmp 004644C2
:004644A8 90
nop
:004644A9 90
nop
:004644AA 90
nop
:004644AB 8B2524AF4900
mov esp, dword ptr [0049AF24]
:004644B1 8B2D28AF4900
mov ebp, dword ptr [0049AF28]
:004644B7 5A
pop edx
:004644B8 646789160000
mov fs:[0000], edx
:004644BE 58
pop eax
:004644BF 33C0
xor eax, eax
:004644C1 C3
ret
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004644A6(U)
|
:004644C2 B804000000
mov eax, 00000004
:004644C7 BD4B484342
mov ebp, 4243484B
:004644CC CC
int 03
:004644CD 5A
pop edx
:004644CE 646789160000
mov fs:[0000], edx
:004644D4 5A
pop edx
:004644D5 B801000000
mov eax, 00000001
:004644DA C3
ret
Ce qui est surprennant, la première fois qu'on voit cela sous Softice,
c'est que quand on trace sur l'INT 03, le programme continue comme si de rien était, sans jamais redonner
la main à Sice. Donc, il va falloir émuler et/ou forcer l'Exception. Mais avant, il faut bien comprendre
chaque morceau de la fonction copiée ci-dessus.
La première partie installe un SEH :
:0046448D 33D2 xor
edx, edx ;edx = 0
:0046448F 68AB444600 push 004644AB ;@ de l'exeption
handler
:00464494 64FF32 push
dword ptr fs:[edx] ;@ de la structure err précédente
:00464497 892524AF4900 mov dword ptr [0049AF24], esp ;sauve l'esp
courant
:0046449D 892D28AF4900 mov dword ptr [0049AF28], ebp ;sauve l'ebp
courant
:004644A3 648922 mov
dword ptr fs:[edx], esp ;set le SEH ( edx = 0 => fs:[0] )
J'ai divisé cette sous-fonction en 3 parties :
1. la première créé
la structure ERR.
Cette structure est définie ainsi :
1er dword +0 Pointeur vers la structure
ERR suivante
2eme dword +4 Pointeur vers l'exception handler
Mais cette structure n'est pas limitée, on peut très bien lui rajouter
un 3ème dword, un 4ème, etc.. contenant du data que nous voudrions passer au SEH. Quand le per-thread
handler est appelé, ESP pointe vers une structure définie comme suit :
ESP+4 Pointeur vers une structure EXCEPTION_RECORD
ESP+8 Pointeur vers la structure ERR correspondant a l'expetion handler
ESP+C Pointeur vers une structure CONTEXT
On peut donc récupèrer le data rajouté à la structure
ERR en faisant :
mov eax, [esp+8]
add eax, 8
A ce moment, EAX pointe sur le 3ème dword de la structure ERR.
2. la seconde sauve ESP et EBP
Pourquoi sauver ces 2 registres ? On verra un peu plus loin que l'ont n'est pas
obligé de quitter l'handler par la voie normale. Je m'explique, en temps normal, un Exption handler doit
traiter l'exception. S'il arrive à la traiter, il RETourne avec 0 dans eax et le thread reprend son exécution
à l'address pointée par le variable RegEIP de la structure CONTEXT envoyée en paramètre
par le registre ESP à l'handler. S'il n'arrive pas à la traiter, il renvoit 1 dans EAX et c'est l'handler
suivant dans la chaine des structures ERR, ou à défaut le Final Exception Handler qui va essayer
de traiter l'exception (tout cela étant géré par une fonction interne à Windows ).
Mais on n'est pas obligé de quitter la procédure ainsi, on peu aussi envoyer un ESP / EBP sure à
l'handler, qui va récupérer ces 2 valeurs, et faire un simple RET. et le programme va continuer son
exécution à l'adresse pointée par [esp]. ( comme normal ). Un exemple :
start:
Call Exemple1
;Appel la procédure Exemple1
invoke ExitProcess, 0 ;Termine le programme
Exemple1 proc
push esp
;Créé une structure ERR
push ebp
push ADDR MyEHandler
push FS:[0]
mov FS:[0], esp ;Set le SEH
xor eax, eax ;Créé une Exception Divide by 0
div eax
MyEHandler:
mov
esp, [esp+8];Se positionne sur le 3eme dword de la structure ERR
add
esp, 8
pop
ebp ;Récupère
l'EBP qui était dans la structure ERR dans EBP
pop
esp ;idem pour
ESP
ret
Exemple1 endp
end start
Donc ce mini-prgramme ne fait rien du tout à par une Exception Divide By
0. La première partie de la procédure Exemple1 va créer une structure ERR ainsi :
ERR+0 NextERR
ERR+4 ADDR MyEHandler
ERR+8 EBP
ERR+C ESP
et lorsqu'on met ESP sur la pile (dans la structure ERR), [ESP] est l'address
de retour de la procédure Exemple1. Quand l'exception est créé, notre Handler restaure les
registres ESP et EBP dans l'état qu'ils avaient au début de la procédure Exemple1 et donc
[ESP] pointe sur l'address de retour de Exemple1. Le RET va nous emmener non plus dans la fonction de gestion des
Exception's de Windows, mais juste àprès le Call Exemple1 et le programme va continuer sont exécution
comme si la procédure Exemple1 c'était terminée normalement.
Rem : contrairement à ce que
j'ai déjà entendu, un SEH n'est pas exécuté en Ring0 !!! Sinon cette méthode
ne serait pas possible, car le programme continuerait sont exécution en Ring0 --> plantage !
Si vous n'avez pas bien compris ce point 2, je vous conseil de le relire et d'essayer
de bien l'asimiler car la protection de Blind Read est basée sur ce principe !
3. Set le SEH
En fait, quand un Thread est crée, il y a plusieurs informations sur celui-ci
qui sont sauvées dans une structure TIB (Thread Information Bloc) et cette structure TIB se trouve en fs:[0].
Cette Structure fait partie de la structure Thread Database qui contient toutes les informations nécessaires
à l'exécution du Thread. Le premier dword du TIB est un pointeur vers une structure ERR définissant
le premier per-thread SEH de la chaine. Lorsqu'il y a une Exception, Windows rentre dans une boucle du type de
celle-ci :
mov eax, fs:[0] ;met le pointeur vers la première structure ERR de la chaine dans EAX
NextERR:
cmp dword ptr[eax], -1 ;regarde si il y a encore une structure ERR ( -1 si il y en a pas )
je Final_Exception_Handler ;si non, il exécute le Final_Exception_Handler ( Unhandled one )
push eax ;sauve EAX
push pContext ;ESP+C est un pointeur sur une structure CONTEXT
push eax ;ESP+8 est le pointeur sur la structure ERR correspondante
à l'handler
push pExceptionRecord ;ESP+4 est le pointeur vers l'exception la structure
EXCEPTION_RECORD
call dword ptr[eax+4] ;et on appel l'Exception Handler de la structure ERR.
test eax, eax ;Test la valeur de retour
pop eax ;restaure EAX qui pointe vers la structure ERR courante
jz RetournToThread ;Si la fonction renvoit 0 c'est que l'handler a traité l'exception
;et l'exécution du Thread peu continuer
mov eax, [eax] ;sinon on met dans eax le pointeur vers la structure ERR suivante
jmp NextERR ;et on boucle.
Evidemment, cette fonction n'est pas celle de Windows, mais c'est pour vous donner
une idée simpliste du principe.
Donc, si on revient à la 1ere partie de la fonction de Blind Read
:0046448D 33D2 xor
edx, edx ;edx = 0
:0046448F 68AB444600 push 004644AB ;@ de l'exeption handler
:00464494 64FF32 push
dword ptr fs:[edx] ;@
de la structure err précédente
:00464497 892524AF4900 mov dword ptr [0049AF24], esp ;sauve l'esp courant
:0046449D 892D28AF4900 mov dword ptr [0049AF28], ebp ;sauve l'ebp courant
:004644A3 648922 mov
dword ptr fs:[edx], esp ;set le SEH ( edx
= 0 ==> fs:[0] )
:004644A6 EB1A jmp
004644C2 ;saute directement à la partie 3
La structure ERR est minimaliste, juste l'address de l'handler et celui de la
structure ERR suivante ( 2 dwords ). Ensuite, ESP et EBP sont sauvés dans des variable globale et le SEH
est installé. Pour la suite du cours, j'appellerai [0049AF24] [ESP_Backup] et l'autre [EBP_Backup] pour
une meilleur compréhension. On peut maintnant reguarder la deuxième partie de la fonction :
:EHANDLER
8B2524AF4900 mov esp, dword ptr [ESP_Backup]
;restaure la valeur de ESP et EBP
8B2D28AF4900 mov ebp, dword ptr [EBP_Backup]
;[esp] est le pointeur vers la structure ERR
:004644B7 5A pop
edx ;le
ptr vers la struc ERR suivante dans EDX
:004644B8 646789160000 mov fs:[0000], edx ;Set le SEH avec la struc pointée par EDX
;il enlève donc le SEH crée par la 1ere partie
:004644BE 58 pop
eax ;l'address
de l'handler dans EAX
;sert uniquement a restaurer la pile
:004644BF 33C0 xor
eax, eax ;met EAX à
0
:004644C1 C3 ret ;retourne
On voit donc que comme j'avais expliqué au point deux un peu plus haut,
cet handler ne quitte pas par la methode normal, il reprend un esp/ebp sure et retourne comme si c'était
une fonction normal. Reguardons enfin la 3ème partie :
:004644C2 B804000000 mov eax,
00000004 ;met 4 dans EAX
:004644C7 BD4B484342 mov ebp, 4243484B ;met BCHK dans EBP
:004644CC CC int
03 ;créé une exception BreakPoint
:004644CD 5A pop
edx ;le
ptr vers la struc ERR suivante dans EDX
:004644CE 646789160000 mov fs:[0000], edx ;Set le SEH avec la struc pointée par EDX
;il enlève donc le SEH crée par la 1ere partie
:004644D4 5A pop
edx ;l'address
de l'handler dans EDX
;sert uniquement a restaurer la pile
:004644D5 B801000000 mov eax, 00000001 ;met 1 dans EAX
:004644DA C3 ret ;retourne
Donc, l'INT 03 est appelé avec BCHK, donc Softice ne va pas créé
d'exception, et le programme va continuer sont exception. Si Softice n'est pas chargé, l'exception va avoir
lieu, et l'exception handler en de la structure ERR en fs:[0] va être appelé. Cette handler c'est
la 2eme partie que j'ai analysé ci-dessus. Cette fonction va donc retourner avec 1 dans EAX si Softice est
chargé et 0 dans le cas contraire.
On voit qu'au moment ou l'INT 03 est exécuté, ESP pointe toujours
vers la structure ERR fraichement crée. Vu que l'ExceptionHandler n'utilise pas les structures CONTEXT,
ERR et EXCEPTION_RECORD, on peut emuler très facilement l'exception, pour cela, nous allons simplement faire
un CALL ou un JMP vers l'Exception Handler. On sait que ESP pointe vers la structure ERR. dans l'address de l'handler
est en ERR+4, soit en [ESP+4] et donc, il suffit de remplacer l'INT 03 par
add esp, 04 ;ESP pointe alors sur ERR+4
ret
;et on saute en ERR+4 et donc sur l'handler.
Dans ton cours, Christal, tu as simplement modifier le INT 03 par un RET. Alors
faudrait maintenant comprendre pourquoi ca a fonctionner ? La raison est toute simple, c'est que tu as sauté
sur la pile, l'EIP est donc devenu l'address de ta structure ERR et donc, c'est le code représenté
par le contenu de cette structure qui a été exécuté. Evidemment, c'est pas censé
être du code, et il y a donc eu une Exception qqconque mais il se pourrait très bien que le code représenté
ne fasse pas d'exception et que ton code à ce moment là boucle infiniment, ou je ne sais pas tout
ce qu'il peut faire. Donc, ta modification fonctionne dans ce cas-ci, tant mieux, mais ce n'est pas correct.
Maintenant, imaginez que le programme soit comme celui que j'avais écris
plus haut Exemple1:
start:
Call Exemple1
;Appel la procédure Exemple1
invoke ExitProcess, 0 ;Termine le programme
Exemple1 proc
push esp
;Créé une structure ERR
push ebp
push ADDR MyEHandler
push FS:[0]
mov FS:[0], esp
;Set le SEH
mov ebp, 'KHCB'
;Créé une Exception BreakPoint avec ebp=BCHK
mov eax, 4
int 03
pop eax
;Enlève le SEH et retourne avec EAX=1
mov dword ptr fs:[0], eax
pop eax
xor eax, eax
inc eax
ret
MyEHandler:
mov
esp, [esp+8];Se positionne sur le 3eme dword de la structure ERR
add
esp, 8
pop
ebp ;Récupère l'EBP qui était dans la structure
ERR dans EBP
pop
esp ;idem pour ESP
pop eax ;Enlève le SEH et retourne avec EAX=0
mov
dword ptr fs:[0], eax
pop
eax
xor
eax, eax
ret
Exemple1 endp
end start
A ce moment, là, si vous remplacer le INT 03 par un ADD ESP, 04 / RET,
il y aura une erreur. Vous devinez pourquoi ? Simplement parce que MyEHandler fait référence à
la structure ERR passée normallement en paramètre. Si vous patchez ainsi, la Structure ERR ne se
trouvera pas en [esp+8] et il y aura donc une erreur. Dans un tel cas, 2 solutions s'offrent à vous, soit
vous mettez vous-même les information sur la pile avant d'appeler la fonction, et dans ce cas, je vous souhaite
grand plaisir, soit vous forcez l'Exception. Comment la forcer ? C'est certainement une bonne question! Il faut
la forcer en utilisant une Exception qui ne sera pas arrêtée par Softice, soit une Divide by 0, soit
un accés memoire non authorisé, etc.. Par exemple, vous pouvez remplacer le INT 03 par XOR EAX, EAX
/ DIV AX. A ce moment, il y aura bien une Exception et Windows va se charger pour vous de mettre les infos sur
la pile et d'appeler l'handler. Maintenant, voyons comment compliquer un peu les chose, et imaginez que MyEHandler
soit codé comme suit :
MyEHandler:
mov ebx, [esp+4]
assume EBX:PTR EXCEPTION_RECORD
xor eax, eax
cmp [ebx].ExceptionCode,
EXCEPTION_BREAKPOINT
jz @B
inc eax
@@:
mov esp, [esp+8]
add esp, 8
pop ebp
pop esp
ret
Là, nous avons un problème... Car si on force l'Exception avec une
autre Exception, l'Handler va renvoyer 1 dans EAX ==> bad guy ! Donc nous ne pouvons pas forcer ainsi l'Exception.
A Propos, ceci est une méthode utilisée pour la détection de Frogsice qui force l'Exception
par un Divide by 0.
Mais notre problème à présent, c'est d'arriver à simuler
une Exception INT 03.
Rem : ce qui suit n'est que théorique,
je n'ai jamais testé cette méthode.
Et là, je dis vive Microsoft ! :) Un petit coup d'oeil dans la références
des APIs et on trouve ceci :
The RaiseException function raises an exception in the calling thread.
VOID RaiseException(
DWORD dwExceptionCode, // exception code
DWORD dwExceptionFlags, // continuable exception
flag
DWORD nNumberOfArguments, // number of arguments
in array
CONST DWORD *lpArguments // address of array
of arguments
);
Il suffit donc de remplacer le INT 03 par
invoke RaiseException, EXCEPTION_BREAKPOINT, NULL, NULL, NULL
Je n'ai jamais testé cette méthode.. je ne peux dès lors
pas vous dire si elle fonctionne.. mais théoriquement, elle devrait. J'attends un test de votre part !
L'autre solution dans un tel cas, c'est de bien comprendre les éléments
utilisés ( ici, la structure ERR et l'ExceptionCode de la structure EXCEPTION_RECORD ) et de ne remplir
que ces structures-là et appeler soit-même l'Exception... Mais encore une fois, il y aura un problème
si l'Handler retourne par la voit normal ( la fonction de gestion des EH de Windows ). Donc à ce moment,
il faut modifier soit MyEHandler pour qu'il renvoit une bonne valeur (dans le cas de mon programme Exemple, c'est
EAX=0), soit modifier la fonction pour qu'elle renvoit une bonne valeur. Dans mon exemple, il faudrait soit nopper
l'INC EAX de MyEHandler et le INC EAX de la procédure Exemple, soit mettre un XOR EAX, EAX / RET à
l'EntryPoint de la fonction car dans le fond, cette fonction ne sert a rien. Donc, si on applique cela à
la fonction de Blind Read, nous avons à présent 4 manières de patcher :
1.
:004644CC
add esp, 04 ;Emule l'exception
ret
2.
:004644CC
xor eax, eax ;Force l'exception
div ax
3.
:0046448D
xor eax, eax ;renvoit toujours EAX=0
ret
4.
:004644D5
mov eax, 00000000 ;idem
Rem : Une
cinquième possibilitée serait de faire
5.
:004644CC
push 0 ;Raise l'exception
push 0
push 0
push 80000003 ;EXCEPTION_BREAKPOINT
call RaiseException
Maintenant, je vais vous montrer comment tracer un programme qui utilise de telles
Exceptions. Exemple : BlindRead évidemment.
Sous Softice, on arrive facilement à la fonction de détection par BCHK/INT03
dont revoici le code :
0177:0046448D 33D2
XOR EDX,EDX
0177:0046448F 68AB444600
PUSH 004644AB
0177:00464494 64FF32
PUSH DWORD PTR FS:[EDX]
0177:00464497 892524AF4900
MOV [0049AF24],ESP
0177:0046449D 892D28AF4900
MOV [0049AF28],EBP
0177:004644A3 648922
MOV FS:[EDX],ESP
0177:004644A6 EB1A
JMP 004644C2
0177:004644A8 90
NOP
0177:004644A9 90
NOP
0177:004644AA 90
NOP
0177:004644AB 8B2524AF4900
MOV ESP,[0049AF24]
0177:004644B1 8B2D28AF4900
MOV EBP,[0049AF28]
0177:004644B7 5A
POP EDX
0177:004644B8 646789160000
MOV FS:[0000],EDX
0177:004644BE 58
POP EAX
0177:004644BF 33C0
XOR EAX,EAX
0177:004644C1 C3
RET
0177:004644C2 B804000000
MOV EAX,00000004
0177:004644C7 BD4B484342
MOV EBP,4243484B
0177:004644CC CC
INT 3
0177:004644CD 5A
POP EDX
0177:004644CE 646789160000
MOV FS:[0000],EDX
0177:004644D4 5A
POP EDX
0177:004644D5 B801000000
MOV EAX,00000001
0177:004644DA C3
RET
Lorsqu'on se trouve sur 0177:004644CC, une Exception doit avoir lieu. Cette Exception
n'aura lieu que si vous tracez avec F10. Sinon, Softice ne créera pas d'Exception. Donc, nous allons mettre
un BreakPoint sur l'Handler. Dans ce cas-ci, c'est très simple car l'installation du SEH se trouve just
au-dessus et on voit directement que l'handler est en 004644AB. Mais bon, faisons comme si nous ne savions pas.
Pour retrouver son adresse, nous faisons :
:dd fs:0
1A0F:00000000 0071:FC00 0072:0000 0071:B000 40B7:215E
..q...r...q.^!.@
1A0F:00000010 0000:0000 0000:0000 8174:517C FFFF:0001
........|Qt.....
1A0F:00000020 0000:0000 D75F:60D1 0000:220F 8174:5204
.....`_.."...Rt.
1A0F:00000030 8174:40E8 8000:6498 ????:???? ????:????
.@t..d..........
Ce dump de la mémoire est en fait le TIB correspondant au Thread de BlindRead.
Si vous avez bien compris tout ce cours, vous savez dès lors que le 1er dword : 0071FC00 est le pointeur
vers la 1ere structure ERR de la chaine.
:dd ds:0071fc00
017F:0071FC00 0071FC44 004644AB 004643CF 00C72B4C
D.q..DF..CF.L+..
017F:0071FC10 00C72B4C 0071FC18 00C79010 00C6DFE4
L+....q.........
017F:0071FC20 00463E38 00000003 004635AF 41DE0D44
8>F......5F.D..A
Il est impossible de savoir quelle est la grandeur de cette structure, mais nous
savons que le 1er dword est le pointeur vers la structure ERR suivante et que le second dword est l'address de
l'handler.
ERR+0 0071FC44
ERR+4 004644AB
ERR+. ...
Nous pouvons à présent mettre, par exemple un BPX sur ce handler
et aller voir son code pour savoir quel est la méthode à utiliser pour contourner cette détection
:
:bpx *(0071fc00+4)
:u 004644ab
0177:004644AB 8B2524AF4900
MOV ESP,[0049AF24]
0177:004644B1 8B2D28AF4900
MOV EBP,[0049AF28]
0177:004644B7 5A
POP EDX
0177:004644B8 646789160000
MOV FS:[0000],EDX
0177:004644BE 58
POP EAX
0177:004644BF 33C0
XOR EAX,EAX
0177:004644C1 C3
RET
Ce handler ne fait usage d'aucune des structures qui lui sont passées en
paramètres et ne retournant pas par la porte normale, nous pouvons donc émuler avec un ADD ESP, 04
/ RET l'exception. Nous pourrions aussi mettre un XOR EAX, EAX / DIV AX et alors notre Breakpoint nous servirait
à qqchose.
:a EIP
:xor eax, eax
:div ax
:
:bl
03) BPX #017F:004644AB
un appuis sur F5 devrait vous amener tout droit à l'Exception Handler. Maintenant,
imaginons que vous voudriez mettre un BPX sur tous les Exception Handler, il suffit de faire
:dd ds:0071fc00
017F:0071FC00 0071FC44 004644AB 004643CF 00C72B4C
D.q..DF..CF.L+..
:bpx 004644AB
:dd ds:0071FC44
017F:0071FC44 0071FC84 00463942 0071FC64 00000010
..q.B9F.d.q.....
:bpx 00463942
:dd 71fc84
017F:0071FC84 0071FC90 0040F8AF 0071FCA8 0071FCB0
..q...@...q...q.
ainsi de suite. On remarque qu'il y une longue liste chainée. Voici le
liens entre les tructures ERR suivante :
017F:0071FC90 0071FCB0 0040F8CE
-> 017F:0071FCB0 0071FCBC 00446884
--> 017F:0071FCBC 0071FCC8 004468BC
---> 017F:0071FCC8 0071FCD4 004468DC
----> 017F:0071FCD4 0071FE04 004032AD
-----> 017F:0071FE04 0071FE2C 0044D772
------> 017F:0071FE2C 0071FF68 00403840
-------> 017F:0071FF68 FFFFFFFF <-- -1 = Fin de la liste
chainée
Pour terminer, Une manière d'automatiser tout ca, on peut utiliser une
macro sous Softice qui va mettre un BPX sur le premier SEH a chaque fois qu'il est modifié. Ainsi, on bloquera
a chaque fois qu'il y a une Exception.
:bpmd fs:0 W if eip<80000000 do "bc 01;bpx *(esp+4);x;"
Donc il met un BreakPoint on memory Write Acces en fs:0. le IF eip<80000000
c'est pour ne bpxé que les SEH du process ( et pas ceux de USER32 / Kernel32 ). et lorqu'il y a accés
en écriture, il enlève le BP précédent et en met un nouveau sur l'address de l'handler
dans la nouvelle structure ERR ( supposée en [ESP] ). Donc vous mettez ca au début du prog, il ne
s'arretera que lorsqu'il y aura une Exception. Je ne vous conseille pas de le faire dès le début..
mais vous pouvez toujours essayer de l'utiliser lorsque vous vous situez dans la routine anti-sice.
Pour terminer, il faudrait parler de RtlUnwind. Cette fonction consiste, en gros,
à rétablir la pile après l'exécution d'une suite d'Exception imbriquée. Par
exemple, si le 1er handler ne gère pas l'exception, il la passe au second qui pourrait la passer au suivant
qui traite l'erreur et retourne à une Safe Place ou le seul SEH qu'il doit y avoir, c'est le 3eme. Dans
un tel cas, on utilise RtlUnwind. On pourrait évidemment écrire sa propre fonction d'Unwind mais
avant cela, il faudrait connaître le principe de cette fonction :
Tout d'abord, elle construit un Context avec le regEip égal
à l'address de retour donné en paramètre. Ensuite elle construit une structure ExceptionRecord
à partir de celle donnée en paramètre et met l'ExceptionFlags à 2 ( EH_UNWINDING ).
Après cela, elle va appeler chaque EHandler de la chaine en commencant par celui en fs:[0]. Après
chaque exécution, elle va l'enlever de la chaine... jusqu'au moment ou elle arrivera à l'address
de la structure ERR passée en paramètre à la fonction. Pour illustrer le principe, j'ai écris
un petit programme qui ne fait encore rien à part des Exception ( la source complète se trouve ici ):
start:
push offset MyFinalEHandler ;Set SEH
push fs:[0]
mov fs:[0], esp
mov FinalERRStruct, esp
;Save SafeStackframe
mov SafeESP, esp
mov SafeEBP, ebp
mov LastExceptionCode, NO_EXCEPTION
call Except1
;Call Exception Fonction
SafePlace:
;------------------------------------------------------------------------------------------
; Ici vous pouvez mettre du code qui s'exécutera
normalement sans problème
; ou tout au moin indépendemment des
exceptions qui seraient parvenues précédemment
;------------------
; Pour l'exemple, lorsqu'une Exception survient,
sont code est sauvée dans
; LastExceptionCode si c'est pas lors de l'Unwind.
Le SafeCode va tester et afficher le
; contenu de LastExceptionCode.
;------------------------------------------------------------------------------------------
mov eax, LastExceptionCode
.if eax==NO_EXCEPTION
invoke MessageBoxA, 0, ADDR szNoException,
ADDR szLastException, 0
.elseif eax==EXCEPTION_INT_DIVIDE_BY_ZERO
invoke MessageBoxA, 0, ADDR szDivideBy0,
ADDR szLastException, 0
.elseif eax==EXCEPTION_ACCESS_VIOLATION
invoke MessageBoxA, 0, ADDR szAccessViolation,
ADDR szLastException, 0
.endif
pop dword ptr fs:[0]
;Remove SEH
add esp, 4
invoke ExitProcess, NULL
;Exit Process
;----------------------------------------------
; Si l'exception n'a pas été
gèrée, ExitProcess
;----------------------------------------------
MyFinalEHandler:
invoke ExitProcess,
0
La première partie du programme Set un SEH qui sera dès lors le
dernier EH exécuté avant de passer la main au UnhandledExceptionFilter ( je n'explique pas sont utilisation
vu qu'elle est très bien expliquée dans la Refs des API Win32 ). Ensuite, vu qu'on se trouve dans
un endroit Safe, il sauve le StackFrame ( EBP / ESP ) ainsi que l'address de notre Structure ERR Finale pour donner
en paramètre plus tard à RtlUnwind. LastExceptionCode recevra le code de la dernière exception
survenue et est donc initialisée à NO_EXCEPTION ( -1 ). Le Handler de notre FinalERRStruct va simplement
terminer le Process car si on arrive à lui, c'est qu'aucun de nos EHandler n'a gèré l'exception.
La SafePlace se trouve juste après l'appel de la fonction supposée à risque Except1 dont voici
le code :
;------------------------------------------------------------------------
; Except1 PROC - Supposons qu'elle utilise
une zone mémoire à risque
;------------------------------------------------------------------------
; Install un SEH qui gère les exception
Access Violation
; Et appel la fonction Except2
;------------------------------------------------------------------------
Except1 proc
push offset @@EHandler
;Set SEH
push fs:[0]
mov fs:[0], esp
call Except2
;Call next Except Func
pop dword ptr fs:[0]
;Remove SEH
add esp, 4
ret
;----------------------------------------------
; Gère uniquement
les violations mémoire
;----------------------------------------------
@@EHandler:
mov eax, [esp+04]
assume eax:PTR EXCEPTION_RECORD
test [eax].ExceptionFlags, 2
jnz @F
push [eax].ExceptionCode
pop LastExceptionCode
@@:
cmp [eax].ExceptionCode, EXCEPTION_ACCESS_VIOLATION
jz @F
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
@@:
invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szException, 0
;RtlUnwind va enlever les SEH jusqu'au FinalERRStruct dans la chaine
; et appelera chaque Handler enlevé avec comme paramètre
; ExceptionFlag = EH_UNWINDING
mov eax, esp
push NULL ;Return Value //
unused
add eax, 0Ch
push dword ptr[eax] ;pExceptionRecord
push offset @F ;Return Addr
sub eax, 4
push FinalERRStruct ;pErrStruct
call RtlUnwind ;Unwind
@@:
; Ici, je propose 2 manière de se retrouver à la SafePlace...
; soit par la petite porte en restaurant le StackFrame
; soit par la voie normale en modifiant le Context
;
; Rem : Une autre possibilitée aurait été de laisser le SafePlace
; --- Restaurer un SafeStackFrame et de mettre l'offset SafePlace
; comme address de retour de RtlUnwind.
; jmp
@@LittleDoor
jmp @@NormalWay
@@LittleDoor:
mov ebp, SafeEBP ;Set SafeStackframe
mov esp, SafeESP
push SafePlace ;Jmp to SafePlace
ret
@@NormalWay:
mov eax, [esp+0Ch]
assume eax:PTR CONTEXT
push SafeEBP ;Restaure un SafeStackFrame
pop [eax].regEbp
push SafeESP
pop [eax].regEsp
push offset SafePlace ;Modifie l'EIP
pop [eax].regEip
mov eax, EXCEPTION_CONTINUE_SEARCH ;On a réparé l'erreur... essayons de rééxécuter
le Thread.
ret
Except1 endp
Commencons par une remarque de syntaxe, l'utilisation des double-arobas permet
de définir des label local. En d'autres mots, si vous mettez @@ devant un label, celui-ci est indépendant
des autres fonctions du programme. De plus, le symbol @F correspond au prochain label local dans la source et @B
correspond au précédent. ( Backward / Forward ).
La fonction Except1 commence par installer un SEH avant d'exécuter
une autre fonction à risque : Except2. Le handler du SEH de Except1 est le plus important et je vais analyser
chaque partie avec vous :
@@EHandler:
mov eax, [esp+04]
assume eax:PTR EXCEPTION_RECORD
test [eax].ExceptionFlags, 2
jnz @F
push [eax].ExceptionCode
pop LastExceptionCode
@@:
Cette première partie sauve l'ExceptionCode dans LastExceptionCode si l'ExceptionFlag
est différent de 2 ou plus précisément si l'Handler n'est pas appelé lors de l'Unwind.
cmp [eax].ExceptionCode, EXCEPTION_ACCESS_VIOLATION
jz @F
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
@@:
Cet handler ne gère que les exception de violation mémoire et donc,
si ce n'est pas une telle exception, il passe la main au prochain SEH dans la chaine, soit l'Handler de notre FinalERRStruct.
invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szException, 0
;RtlUnwind va enlever les SEH jusqu'au FinalERRStruct dans la chaine
; et appelera chaque Handler enlevé avec comme paramètre
; ExceptionFlag = EH_UNWINDING
mov eax, esp
push NULL ;Return Value //
unused
add eax, 0Ch
push dword ptr[eax] ;pExceptionRecord
push offset @F ;Return Addr
sub eax, 4
push FinalERRStruct ;pErrStruct
call RtlUnwind ;Unwind
@@:
Il affiche ensuite l'erreur dans une MessageBox et appel RtlUnwind. RtlUnwind
recoit en paramètre le pointeur vers l'ExceptionRecord passé en paramètre à l'EHandler
et l'address de retour est juste après le call RtlUnwind. RtlUnwind va ensuite appeler chaque EHandler jusqu'au
moment ou elle arrivera à notre FinalERRStruct qui redeviendra le premier SEH de la chaine.
; jmp
@@LittleDoor
jmp @@NormalWay
@@LittleDoor:
mov ebp, SafeEBP ;Set SafeStackframe
mov esp, SafeESP
push SafePlace ;Jmp to SafePlace
ret
@@NormalWay:
mov eax, [esp+0Ch]
assume eax:PTR CONTEXT
push SafeEBP ;Restaure un SafeStackFrame
pop [eax].regEbp
push SafeESP
pop [eax].regEsp
push offset SafePlace ;Modifie l'EIP
pop [eax].regEip
mov eax, EXCEPTION_CONTINUE_SEARCH ;On a réparé l'erreur... essayons de rééxécuter
le Thread.
ret
Except1 endp
La je vous laisse le choix de la méthode à utiliser pour revenir
à la SafePlace. Soit vous restaurez le StackFrame manuellement et sautez à l'address de la SafePlace,
soit vous modifier le Context du Thread et vous essayez de relancer l'exécution ( en renvoyant EXCEPTION_CONTINUE_SEARCH
dans eax ). Allons voir maintenant le code de la seconde fonction à risque :
;------------------------------------------------------------------------
; Except2 PROC - Supposons qu'elle utilise
des divisions d'entier ( DIV )
;------------------------------------------------------------------------
; Install un SEH qui gère les exception
Divide By Zero
; Et créé soit une Exception
Divide By Zero
;
soit une Exception Access Violation
;------------------------------------------------------------------------
Except2 proc
push esp
;SetSEH
push ebp
push offset @@EHandler
push fs:[0]
mov fs:[0], esp
;---------------------------------------------
; Mettez un ';' devant
l'exception que vous ne
; voulez pas créer.
;---------------------------------------------
xor eax, eax
; div ax
;Raise a Divide by Zero Exception
mov dword ptr[eax],
eax ;Raise a Access Violation Exception
pop dword ptr fs:[0]
;Remove SEH
add esp, 0Ch
ret
;----------------------------------------------
; Gère uniquement
les divisions par 0
;----------------------------------------------
@@EHandler:
mov eax, [esp+4]
assume eax:ptr EXCEPTION_RECORD
test [eax].ExceptionFlags, 2
jnz @F
push [eax].ExceptionCode
pop LastExceptionCode
@@:
cmp [eax].ExceptionCode, EXCEPTION_INT_DIVIDE_BY_ZERO
jz @F
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
@@:
invoke MessageBoxA, 0, ADDR szDivideBy0, ADDR szException, 0
mov esp, [esp+08]
pop dword ptr fs:[0] ;Remove SEH
pop ebp
pop ebp
;Restore StackFrame
pop esp
ret
Except2 endp
Cette deuxième fonction crée un troisième SEH qui va gérer
les exception de division par 0. Le StackFrame est sauvé dans la structure ERR du SEH pour pouvoir retourner
de la fonction sans problème même en cas d'exception.
Le Handler va tester si c'est bien une exception Divide By Zero, dans le cas
contraire il renvoit l'exception au prochain EHandler dans la chaine. Si c'est une telle excption, il l'affiche
dans une MessageBoxA et restaure le StackFrame qui se trouve dans la structure ERR du handler. Un simple RET correspondra
dès lors à un retour normal de la fonction Except2. On pourrait par exmple mettre -1 dans EAX pour
prévenir la fonction Except1 qu'il y a eu une erreur mais en aucun cas, l'exception aura une conséquence
sur l'exécution de la fonction Except1
Voilà, c'est enfin fini.. j'espère que je n'ai pas été
trop flou sur tel ou tel point. Si vous désirez plus d'expliquations, n'hésitez pas : teejee@hotmail.com.
Amicalement,
TeeJi
SEH.ASM (c)2000 TeeJi
;==========================================================================================
; SEH programme de démonstration des listes chainées de SEH et utilisation
de RtlUnwind
;==========================================================================================
; Le programme Install un SEH qui sera le FinalEH et qui ne fera que quitter le programme.
; Ensuite la fonction Except1 est appelée, et supposons qu'elle utilise une zone
mémoire
; à risque, j'ai donc installé un SEH qui gère ce type d'eception.
Cette fonction Except1
; fait appel à la fonction Except2, et supposons que celle-ci fasse des opérations
mathémaique,
; j'ai donc installé un SEH qui gère les exceptions Divide By Zero.
;
; Si une Exception Divide By Zero survient dans la fonction Except2, le SEH de Except2
va
; restauré le StackFrame et retourner comme si de rien était.
;
; Si une Exception Acces Violation survient, alors le SEH de Except2 va passer la main
à
; l'SEH de Except1 qui va utiliser Unwind pour restaurer le StackFrame et enlever le
SEH
; de l'Except2 et de l'Except1, ensuite remettre un SafeStackFrame et sauté à
un Code Safe.
;
; Rem : RtlUnwind enlève les Structures ERR situées avant FinalERRStruct
et exécute chaque
; --- Handler pointé par les ERR Struct enlevées avec en param
ExceptionRecord.ExceptionFlags
; égale à EH_UNWINDING pour le prévenir
de l'Unwind. Mes SEH ne doivent rien faire
; lorsqu'il y a Unwinding mais vous pourriez par
exemple modifier une variable, ou
; arrêter un Thread, ou créer une nouvelle
Window, etc... Libre à vous maintenant
; de l'utiliser comme bon vous semble !
;==========================================================================================
;
(c)2000 TeeJi
;==========================================================================================
.486
.model flat, stdcall
option casemap :none ; case sensitive
assume fs : nothing
; #########################################################################
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data?
FinalERRStruct dd ?
SafeEBP
dd ?
SafeESP
dd ?
LastExceptionCode dd ?
.data
szException db "Exception occurs !",0
szAccessViolation db "Access Violation",0
szDivideBy0 db "Divide by 0",0
szLastException db "Last Exception Before SafePlace",0
szNoException db "No Exception",0
.const
NO_EXCEPTION equ -1
.code
start:
push offset MyFinalEHandler ;Set SEH
push fs:[0]
mov fs:[0], esp
mov FinalERRStruct, esp ;Save SafeStackframe
mov SafeESP, esp
mov SafeEBP, ebp
mov LastExceptionCode, NO_EXCEPTION
call Except1
;Call Exception Fonction
SafePlace:
;-----------------------------------------------------------------------------------
; Ici vous pouvez mettre du code qui s'exécutera normalement
sans problème
; ou tout au moin indépendemment des exceptions qui seraient
parvenues précédemment
;------------------
; Pour l'exemple, lorsqu'une Exception survient, sont code est sauvée
dans
; LastExceptionCode si c'est pas lors de l'Unwind. Le SafeCode va
tester et afficher
; le contenu de LastExceptionCode.
;------------------------------------------------------------------------------------
mov eax, LastExceptionCode
.if eax==NO_EXCEPTION
invoke MessageBoxA, 0, ADDR szNoException, ADDR szLastException,
0
.elseif eax==EXCEPTION_INT_DIVIDE_BY_ZERO
invoke MessageBoxA, 0, ADDR szDivideBy0, ADDR szLastException,
0
.elseif eax==EXCEPTION_ACCESS_VIOLATION
invoke MessageBoxA, 0, ADDR szAccessViolation, ADDR szLastException,
0
.endif
pop dword ptr fs:[0]
;Remove SEH
add esp, 4
invoke ExitProcess, NULL ;Exit Process
;------------------------------------------------------------------------
; Except1 PROC - Supposons qu'elle utilise une zone mémoire
à risque
;------------------------------------------------------------------------
; Install un SEH qui gère les exception Access Violation
; Et appel la fonction Except2
;------------------------------------------------------------------------
Except1 proc
push offset @@EHandler ;Set SEH
push fs:[0]
mov fs:[0], esp
call Except2
;Call next Except Func
pop dword ptr fs:[0] ;Remove
SEH
add esp, 4
ret
;----------------------------------------------
; Gère uniquement les violations mémoire
;----------------------------------------------
@@EHandler:
mov eax, [esp+04]
assume eax:PTR EXCEPTION_RECORD
test [eax].ExceptionFlags,
2
jnz @F
push [eax].ExceptionCode
pop LastExceptionCode
@@:
cmp [eax].ExceptionCode,
EXCEPTION_ACCESS_VIOLATION
jz @F
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
@@:
invoke MessageBoxA,
0, ADDR szAccessViolation, ADDR szException, 0
;RtlUnwind va enlever
les SEH jusqu'au FinalERRStruct dans la chaine
;
et appelera chaque Handler enlevé avec comme paramètre
;
ExceptionFlag = EH_UNWINDING
mov eax, esp
push NULL
;Return Value // unused
add eax, 0Ch
push dword ptr[eax]
;pExceptionRecord
push offset @F
;Return Addr
sub eax, 4
push FinalERRStruct
;pErrStruct
call RtlUnwind
;Unwind
@@:
; Ici, je propose 2 manière
de se retrouver à la SafePlace...
; soit par la petite
porte en restaurant le StackFrame
; soit par la voie
normale en modifiant le Context
;
; Rem : Une autre
possibilitée aurait été de laisser le SafePlace
; ---
Restaurer un SafeStackFrame et de mettre l'offset SafePlace
;
comme address de retour de RtlUnwind.
; jmp @@LittleDoor
jmp @@NormalWay
@@LittleDoor:
mov ebp, SafeEBP
;Set SafeStackframe
mov esp, SafeESP
push SafePlace
;Jmp to SafePlace
ret
@@NormalWay:
mov eax, [esp+0Ch]
assume eax:PTR CONTEXT
push SafeEBP
;Restaure un SafeStackFrame
pop [eax].regEbp
push SafeESP
pop [eax].regEsp
push offset SafePlace
;Modifie l'EIP
pop [eax].regEip
mov eax, EXCEPTION_CONTINUE_SEARCH
;On a réparé l'erreur... essayons de rééxécuter le Thread.
ret
Except1 endp
;------------------------------------------------------------------------
; Except2 PROC - Supposons qu'elle utilise des divisions d'entier
( DIV )
;------------------------------------------------------------------------
; Install un SEH qui gère les exception Divide By Zero
; Et créé soit une Exception Divide By Zero
; soit une Exception
Access Violation
;------------------------------------------------------------------------
Except2 proc
push esp
;SetSEH
push ebp
push offset @@EHandler
push fs:[0]
mov fs:[0], esp
;---------------------------------------------
; Mettez un ';' devant l'exception que vous
ne
; voulez pas créer.
;---------------------------------------------
xor eax, eax
div ax
;Raise a Divide by Zero Exception
; mov dword ptr[eax], eax
;Raise a Access Violation Exception
pop dword ptr fs:[0]
;Remove SEH
add esp, 0Ch
ret
;----------------------------------------------
; Gère uniquement les divisions par
0
;----------------------------------------------
@@EHandler:
mov eax, [esp+4]
assume eax:ptr EXCEPTION_RECORD
test [eax].ExceptionFlags,
2
jnz @F
push [eax].ExceptionCode
pop LastExceptionCode
@@:
cmp [eax].ExceptionCode,
EXCEPTION_INT_DIVIDE_BY_ZERO
jz @F
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
@@:
invoke MessageBoxA,
0, ADDR szDivideBy0, ADDR szException, 0
mov esp, [esp+08]
pop dword ptr fs:[0]
;Remove SEH
pop ebp
pop ebp
;Restore StackFrame
pop esp
ret
Except2 endp
;----------------------------------------------
; Si l'exception n'a pas été gèrée, ExitProcess
;----------------------------------------------
MyFinalEHandler:
invoke ExitProcess, 0
end start
|