ex0ns

about
A Computer Science Student's blog, about programming, algorithms, hack and a lot more.

Détection d'un débugger (int 0x2D)

07 Apr 2014

INT 02Dh, vous avez dit debugger ?

Je me suis plongé un peu dans les différentes techniques de detection des debuggers, et souhaite faire partager le premier : l'interruption 2d. Cette interruption provoque la levée d'une exception, si le debugger est présent il va capturer cette exception et le programme ne sera jamais capable de le récupérer, on imagine déjà que l'on pourrait effectuer des opérations qui doivent être executées uniquement si le debugger n'est pas présent dans cette exception, voyons voir comment ceci fonctionne. Tout d'abord, pour le reste de cet article, nous utiliserons MASM et IMM Debugger, ainsi qu'un code assez simple (que je detaillerai à la fin de cet article car ce n'est pas le sujet de cet article) qui fait simplement apparaitre une fenêtre basique avec uniquement un titre. Afin d'avoir un retour visuel de nos différentes expériences, le but sera de modifier le titre de cette fenêtre. Le code de base est le suivant:

.386
.model flat, stdcall
option casemap:none

include windows.inc

include user32.inc
includelib user32.lib

include kernel32.inc
includelib kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.data
ClassName   BYTE    "SimpleWinClass", 0
NoDebugger  BYTE    "A great title",0
Debugger    BYTE    "Please close the debugger",0
Secret      BYTE    "This is a secret title",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
AppName LPCTSTR ?

.code
start:
 invoke GetModuleHandle,NULL
 mov hInstance, eax

 invoke GetCommandLine
 mov CommandLine, eax

 invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

 WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
 LOCAL wc:WNDCLASSEX
 LOCAL msg:MSG
 LOCAL hwnd:HWND

 mov wc.cbSize, SIZEOF WNDCLASSEX
 mov wc.style, CS_HREDRAW OR CS_VREDRAW
 mov wc.lpfnWndProc, OFFSET WndProc
 mov wc.cbClsExtra, NULL
 mov wc.cbWndExtra, NULL
 push hInstance
 pop wc.hInstance
 mov wc.hbrBackground, COLOR_WINDOW+1
 mov wc.lpszMenuName, NULL
 mov wc.lpszClassName, OFFSET ClassName
 invoke LoadIcon,NULL,IDI_APPLICATION
 mov wc.hIcon, eax
 mov wc.hIconSm, eax
 invoke LoadCursor,NULL,IDC_ARROW
 mov wc.hCursor, eax
 invoke RegisterClassEx, addr wc
 mov AppName, offset NoDebugger

 ; Partie importante, génère une interruption
 xor eax, eax
 int 02dh
 inc eax


 startWindow:
 invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\ 
                NULL
 mov hwnd, eax
 invoke ShowWindow,hwnd,CmdShow
 invoke UpdateWindow, hwnd
 .WHILE TRUE
    invoke GetMessage, ADDR msg, NULL, 0, 0
    .BREAK .IF (!eax)
    invoke TranslateMessage, ADDR msg
    invoke DispatchMessage, ADDR msg
 .ENDW
 mov eax, msg.wParam
 invoke ExitProcess,eax
 ret
 WinMain ENDP

 WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage, NULL
    .ELSE
        invoke DefWindowProc, hWnd, uMsg, wParam, lParam 
        ret
    .ENDIF
    xor eax, eax
    ret 

 WndProc endp
end start

Ce code est largement inspiré du magnifique document de NoteWorthy. Essayer de le compiler, une fenêtre blanche avec le titre "A great title".

Afin de connaitre le fonctionnement des exceptions sur Windows, je vous conseil fortement de lire (ou au moins d'y jetter un coup d'oeil) : Microsoft Exception et Exceptions Handling histoire de savoir un peut comment fonctionne la gestions des exceptions sur Windows. Pour les parresseux, un petit récapitulatif: les exceptions sont envoyés à un gestionnaire d'exceptions, qui contient une liste de structure pouvant gérer ces exceptions, si la structure renvoit le code ExceptionContinueExecution (définit dans EXCPT.H) le système considère que l'exception a été gérée et elle n'est pas passée à la structure suivante (et l'execution du programme reprend). La structure qui doit gérer l'exception possède une méthode de callback qui ressemble à :

__cdecl _except_handler(
    struct _EXCEPTION_RECORD *ExceptionRecord,
    void * EstablisherFrame,
    struct _CONTEXT *ContextRecord,
    void * DispatcherContext
);

Nous devons maintenannt définir cette fonction qui sera se chargée de gérer l'exception levée par l'interruption, pour cela voici le code:

PTExceptionHandler proc ExceptionRecord:DWORD, EstablisherFrame:DWORD, ContextRecord:DWORD, DispatcherContext:DWORD
    mov eax, 0
    ret
PTExceptionHandler endp

Avec MASM, le mot clé "proc" décrit une procédure (fonction) suivit de ses arguments, dans notre cas, ceux requis par une fonction de gestion d'exception, la signification de ces arguments n'est pas essentiel mais elle est tout de même detaillée dans les liens précedents.

L'instruction mov eax, 0 permet de renvoyer le signal décrit précedemment : ExceptionContinueExecution afin de dire au système que l'exception a été correctement gérée (elle ne sera donc pas envoyés aux autres structures). Il ne manque plus qu'une étape afin que notre exception soit utilisée par le système, il nous faut l'ajouter à la liste des interruptions, de préférence au début, pour cela, il existe deux directives (code à placer en premier, juste après l'étiquette "start") :

assume FS:NOTHING
push offset PTExceptionHandler
push FS:[0]
mov FS:[0], esp

La première ligne charge le gestionnaire d'exception, la seconde ajoute sur la pile l'addresse de notre fonction de callback chargée de gérer les interruptions la troisième sauvegarde l'addresse de la fonction de callback par défaut (afin de la restaurer à la fin du programme), et la dernière ligne charge notre fonction (ajoutée au sommet de la pile, et donc pointée par ESP) en tant que fonction de gestion d'exception par défaut. Ce que nous voulons faire maintenant est afficher un titre différent en fonction de la présence ou non d'un debugger, si un debugger est présent, nous laisserons le titre par défaut "A great title" dans le cas contraire, nous afficherons le titre "This is a secret title".

Par défaut le titre est "A great title" et nous savons également que si un débugger est présent l'exception ne sera pas exécutée, il nous suffit donc de changer le titre dans ce code de gestion de l'exception, pour le changer en "This is a secret title", ce code se résume en une ligne à placer dans PTExceptionHandler en première ligne :

mov AppName, offset Secret

Ce code résulte en un changement de l'addresse pointée par AppName en faveur de celle contenant le titre spécial. Vous pouvez maintenant essayer, si le code est éxecuté depuis un debugger (IMM par exemple), le titre de la fenêtre est : "A great title", cependant s'il est éxecuté sans debugger, le titre de la fenêtre est changé en "This is a secret title", cela fonctionne bien et nous savons maintenant comment detecter la présence d'un debugger.

Méthode alternative

L'avantage de cette instruction (int 2d) c'est qu'elle nous permet de detecter d'une autre façon un debugger, en effet, lorsque qu'une exception est gérée par le système, le registre EIP (qui pointe la prochaine instruction qui doit être executée) est decrementé de un (et pointe de nouveau vers l'exception que l'on considère comme corrigée), dans le cas d'une interruption int 2d, le registre eip est incrementé de un, ce qui fait que EIP pointe vers l'instruction située juste apprès l'appel à EIP (execution normale du programme). Cependant, si l'exception est gérée par le debugger, EIP n'est pas decrementé de un, mais uniquement incrementé, ce qui engendre le "saut" d'un byte par le registre EIP, ainsi si un debugger est présent l'instruction située après int 2d sera soit tronqué (si elle tient sur plus d'un byte), soit sautée (si elle tient sur un byte). Comment utiliser ceci pour detecter la présence d'un debugger ? Pour cela il nous faut placer une instruction (sur un byte) à la suite de notre interruption afin de différencier les deux cas, si vous regarder le premier code, on remarque la présence de l'instruction juste après notre interruption:

inc eax ; (opcode : 0x40)

Ainsi si le programme est éxécuté normalement, le registre EAX sera incrementé, et vaudra 1, dans le cas contraire, sa valeur ne sera pas changée et restera 0. Voici le code que nous allons utiliser par la suite, il est à mettre juste après l'incrémentation du registre :

.IF eax == 1 ; Pas de débugger
    mov AppName, offset NoDebugger
.ELSE ; Présence d'un débugger
    mov AppName, offset Debugger
.ENDIF

Notez que les macros de MASM simplifient nettement le code, mais nous aurions très bien pu utiliser des instructions de jump avec des étiquettes pour effectuer le même test. Les instructions conditionnelles précédente vont changer le titre de la fenêtre en fonction de la valeur du registre EAX (et donc de l'éxécution ou non de l'instruction inc eax).

A l'execution de ce code, la fenêtre affichera "Please close the debugger" si le debugger est présent, "A great title" dans le cas contraire. Cette méthode permet donc elle aussi de modifier l'execution du programme en fonction des conditions de lancement. Cependant, les techniques précédentes peuvent facilement être contrées facilement par la plupart des débugger. En effet en lui précisant qu'il faut passer cette exception au programme et ne pas la capturer. Par exemple, pour IMM, il suffit lors de l'execution de l'interruption, d'appuyer sur "MAJ+F9" au lieu de "F9" , qui affichera le titre "This is a secret title" même depuis le debugger, nous devons donc trouver quelque chose de plus puissant si nous voulons détécter de façon plus efficace la présence d'un debugger.

Tous les fichiers (en fonction des étapes) sont disponible à cette adresse.