Structured Exception Handling

By LuTiN NoIR

I_ Seh : Présentation et Utilité :
 

  Tout d'abord qu'est-ce qu'une exception. Et bien on peut représenter cela comme une erreur dans un programme qui n'est pas prévue et qui en général bloque et quitte le progrramme par une messagebox de windows, nous prévenant qu'une erreur c'est produit.
  Donc pour éviter ce genre d'erreur dans un programme, il serait intérressant de disposer d'une structure qui permetrait de les gérer. Et bien c'est là qu'interviennent le SEH ou Structured Exception Handling. A l'aide des seh on peut gérer ces exceptions en utilisant des sous routines ou exception handlers. Grace à cela on peut soit informer l'utilisateur qu'une exception à eu lieu, soit reprendre le programme comme si de rien était ou gerer les erreurs survenues. On peut également s'en servir pour les violations d'accès mémoire. Je ne m'étendrais pas sur les différentes exception qui existe, tout simplement car je ne les connait pas et il existe diverses doc là-dessus. Windows gère ces exceptions de différentes façons. Par exemple si vous utilisez softice et bien dès qu'une exception survient softice apparait, et là on peut déterminer ce qui c'est passer. Sinon Windows peut utiliser une exception handler si il y en a une. Toutes les exceptions ne sont pas forcément aussi bien gérer: un crash du système n'est pas impossible.

  Voici un premier exemple d'utilisation des exceptions handlers avec l'aide de l'API SetUnhandledExceptionFilter qui est assez évocatrice : elle permet de gérer ces exceptions :  

.386 
.model flat,stdcall 
option casemap:none
  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 
  TitleMsg    db "Filter",0 
  GoodMsg     db "Fichier créé",0 
  ErrorMsg    db "Fichier non créé",0 
  Fichier     db "test.dat" 
 
;-------------------------------------------------------
.code
start:
  push     offset NoProblem 
  call     SetUnhandledExceptionFilter
  invoke CreateFile,ADDR Fichier,\ 
          GENERIC_READ or GENERIC_WRITE ,\ 
          FILE_SHARE_READ or FILE_SHARE_WRITE,\ 
          NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ 
          NULL 
 
  invoke MessageBox, 0, addr GoodMsg, addr TitleMsg, 0 
  invoke ExitProcess, NULL 
 
;------------------------------------------------------
NoProblem PROC
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0 
  invoke ExitProcess, NULL
NoProblem ENDP
end start

Sur ce petit exemple, si jamais lors de l'appel à CreateFile il y a une exception qui se produit alors le programme affichera que le fichier n'est pas créer, sinon grace au seh il passe à la routine NoProblem. Bien sur ce n'est qu'un exemple, et oui pour qu'il y est une exception lors de l'appel à cette api .....
  On peut également gérer les exceptions d'une autre façon en utilisant  une exception handler "par thread". Cette méthode est avantageuse notamment pour les chaines (j'y reviendrais tout à l'heure) et aussi par le fait quelle est spécifique au thread. Avant d'aller plus loin un exemple devrait éclaircir la situation :

.386 
.model flat,stdcall 
option casemap:none 
assume fs:nothing           ; pour utiliser un seh avec masm
  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 
  TitleMsg    db "Filter",0 
  GoodMsg     db "aucune exception n'a eu lieu",0 
  ErrorMsg    db "une exception a eu lieu",0 
  Fichier     db "test.dat" 
 
;-------------------------------------------------------
.code
start:
  push     offset NoProblem 
  push FS:[0] 
  mov FS:[0], esp
  invoke CreateFile,ADDR Fichier,\ 
          GENERIC_READ or GENERIC_WRITE ,\ 
          FILE_SHARE_READ or FILE_SHARE_WRITE,\ 
          NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ 
          NULL
    pop FS:[0] 
  add esp,4h
  invoke MessageBox, 0, addr GoodMsg, addr TitleMsg, 0 
  invoke ExitProcess, NULL 
 
;------------------------------------------------------
NoProblem PROC 
 
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0 
  invoke ExitProcess, NULL
NoProblem ENDP
end start

C'est le même principe que tout à l'heure sauf que là on met un except handler avec (de même il y a peu de chance pour que l'appel à la fonction createfile créé une exception):
push     offset NoProblem
push FS:[0]
mov FS:[0], esp

Et il faut également le retiré avec :
pop FS:[0]
add esp,4h

 Voilà c'est on ne peut plus simple. Bien sur ce n'est ici qu'un simple exemple, ce n'est pas réelement intégré dans un programme. En effet dans les deux cas le programme fait appel à l'api ExitProcess pour se terminé. Mais les seh peuvent être mieux utilisé par exemple si une exception à lieu lors de l'éxécution d'une routine du programme alors si on a utilisé un seh dans le code pour afficher une erreur, on verra un message d'erreur, et là on est pas obligé de quitter le programme. En effet il est plus judicieux soit de gérer l'exception qui a eu lieu soit de retourner dans le programme comme si de rien était en ayant pris soin de prevenir l'utilisateur que la fonction appelée n'a pu être éxécutée.
  De même il est possible de chainées les seh, c'est à dire que l'on utilise des seh dans les diverses sous routines d'une même routine, pour gérer différentes erreurs.

 Voilà c'est fini pour la présentation des seh. Je le reconnait volontiers, c'est une présentation assez simpliste, je ne parle même pas de l'utilisation de la pile des exception handler par thread chainées... mais j'espère cependant que ce texte vous aura permis de comprendre l'utilité d'un seh dans un programme.

 Si vous désirez découvrir plus profondément l'utilisation des seh dans un programmes ainsi que les exceptions je vous conseil de lire le magnifique document rédigé par Jeremy Gordon, qui est disponible sur http://win32asm.cjb.net .
 
 

II_ SEH: de la gestion des exceptions à la lutte anti-cracking :
 

  Avant d'attaquer cette partie, Christal vient juste de m'envoyer ce qu'il a rédigé sur les seh, et par ailleurs c'est très bien foutu. Il explique en pleu plus en profondeur ce qu'est un seh. Pour ce qui est des techniques anti-crack il  analysé l'int 3 de blindwrite. Pour ma part je suis actuellemnt sur Aspack2.1 qui est protégé par asprotect (je pense qu'il s'agit de la version 1.0). J'orienterais cette partie vers un exemple de programmation.
  Je vais d'abord commencé par parler un peu de la protection asprotect mise sur aspack 2.1 (du même auteur). Tout d'abord il utilise des seh partout, dans toutes ces routines. De plus il y a un anti debugger qui utilise le même principe que pour blindwrite. Je tiens à préciser que c'est Teeji qui m'a mis sur la piste du seh la vielle où christal m'a demandé si je pouvais porter mon attention sur les seh :) . Le problème c'est que je n'ai pas réussi à mettre la main sur l'endroit exact où il est appellé à une interruption 3. Frogsice en mode BulletProof ou en forcant le hook sut l'int 3 lance bien le prog et nous donne bien où cela à eu lieu, mais vu que les addresses mémoires sont variables ...
  Enfin cette protection mais également donné un aperçu différent de l'utilisation des seh (décidement Alexey Solodovnikov fait du bon boulot). En effet il utilise également cela pour une trial period et un key file. Je m'explique: lorsque l'on lance aspasck il affiche le nombre de jours d'utilisation mais lorsque l'on enlève la protection asprotect (par une petite manipulation) et bien le nombre de jours d'utilisation ne s'affiche plus. Pourquoi ? et bien je ne le sais pas encore exactement mais en tracant dans le code d'asprotect j'ai découvert à un endroit une comparaison de date avec la clef d'un fichier (j'ai découvert cela dans la version 1 d'asprotect disponible sur leur site). Je n'ai pas réussi à remettre la main sur cette partie du code. Donc ce que je vais dire là est à prendre au conditionnel (de toute façon on peut qd même l'utilisé), je ne vais d'ailleurs pas m'étendre si aprotect le fait ou non.
  Supposons que l'on veuille créé une protection sur un programme par keyfile:  

.386 
.model flat,stdcall 
option casemap:none 
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 
  TitleMsg    db "Filter",0 
  GoodMsg     db "utilisateur enregistré",0 
  ErrorMsg    db "utilisateur non enregistré",0 
  Fichier     db "keyfile.dat"
.data? 
  buffer db 512 dup(?)
;-------------------------------------------------------
.code
start:
  push     offset NoProblem 
  push FS:[0] 
  mov FS:[0], esp
  invoke _lopen, addr Fichier, OF_READ 
  .if (eax== -1) 
    xor eax, eax 
    mov eax, [eax] 
  .endif 
  pop FS:[0] 
  add esp,4h
  invoke MessageBox, 0, addr GoodMsg, addr TitleMsg, 0 
  invoke ExitProcess, NULL 
 
;------------------------------------------------------
NoProblem PROC 
 
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0 
  invoke ExitProcess, NULL
NoProblem ENDP
end start  

  Dans ce petit exemple si jamais le fichier keyfile.dat n'est pas présent, et bien on créé une exception qui ferait afficher le message utilisateur non enregistré à la du seh précédement installé. Bien sur au coeur d'un programme il faudrait mettre tout ceci dans une routine et ne pas quitté le prog mais utilisé un ret.
  L'autre utilisation faite par Asprotect est l'anti softice. La méthode est exactement la même que celle décrite par
christal, je ne m'étendrais donc pas sur le pourquoi de cette routine mais plutôt comment utilisé une partie du potentiel offert par les seh. Voici un petit exemple :  

.386 
.model flat,stdcall 
option casemap:none 
assume fs:nothing           ; pour utiliser un seh avec masm
  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 
  TitleMsg    db "Filter",0 
  GoodMsg     db "Softice non détecté",0 
  ErrorMsg    db "Softice détecté",0
.data? 
  buffer db 512 dup(?)
;-------------------------------------------------------
.code
start:
  push     offset NoProblem1 
  push FS:[0] 
  mov FS:[0], esp
mov ebp,"BCHK"      ;on met le mot magique dans ebp 
mov ax, 04h 
int 3               ;on crée une erreur 
                    ;à ce moment si sice est lancé on ne va rien voir et 
                    ;tout va se passer normalement, 
                    ;mais on n'atterrira pas dans l'except Handler...
invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
POP FS:[0] 
ADD ESP,4h        ;on enlève l'except handler
invoke ExitProcess, NULL
;-------------------------------------------------------
NoProblem1 PROC
  push     offset NoProblem2 
  push FS:[0] 
  mov FS:[0], esp
  mov ebp,"BCHK" 
  mov ax, 04h 
  int 3 
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  POP FS:[0] 
  ADD ESP,4h 
  invoke ExitProcess, NULL
  
NoProblem1 ENDP
;-------------------------------------------------------
NoProblem2 PROC
push     offset NoProblem3 
  push FS:[0] 
  mov FS:[0], esp
  mov ebp,"BCHK" 
  mov ax, 04h 
  int 3 
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  POP FS:[0] 
  ADD ESP,4h 
  invoke ExitProcess, NULL 
  
NoProblem2 ENDP 
  
;------------------------------------------------------- 
  
NoProblem3 PROC 
 
  push     offset NoProblem4 
  push FS:[0] 
  mov FS:[0], esp
  mov ebp,"BCHK" 
  mov ax, 04h 
  int 3 
  invoke MessageBox, 0, addr ErrorMsg, addr TitleMsg, 0
  POP FS:[0] 
  ADD ESP,4h 
  invoke ExitProcess, NULL
NoProblem3 ENDP
;------------------------------------------------------
NoProblem4 PROC 
 
  invoke MessageBox, 0, addr TitleMsg, addr GoodMsg, 0 
  invoke ExitProcess, NULL
NoProblem4 ENDP
end start

 Là j'ai tout le temps utilisé le même anti-softice mais rien n'empèche de changer, il suffit juste de générer ou pas une exception en fonction du résultat si l'anti-si ne s'en charge pas lui même.
  Bien sur ce n'est qu'une improche simpliste des choses, je n'ai pas compliqué le code, ce qui est bien sur le cas dans une protection, si c'était simple, quel intérrêt d'utiliser des seh pour cela :).

 Voilà, je n'ai fait que survoler le sujet pour vous donner une première approche de la question aussi bien au niveau de l'utilité d'un seh, que de son utilisation dans une protection logiciel. Je ne saurais trop vous conseillé de lire le texte de Jeremy Gordon sur les seh, disponible sur http://win32asm. Sur ce même site il y a également des exemples d'utilisations des seh dans des protections, nettement plus poussé.
 En espérant que ce petit texte puisse servir à quelque chose.

Amicalement

LuTiN NoIR