ex0ns

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

Codes auto-modifiables (self modifying)

12 Apr 2014

Afin de rester dans un contexte de reverse-engineering et de programmation bas niveau, voici un article traitant des codes "auto-modifiables" (self modifying codes), en d'autre termes, il s'agit de rendre un exécutable capable de muter par lui même lors de l'exécution. Cette technique peut être utilisée au lancement d'un programme afin d'optimiser sa vitesse d'exécution. Wikipedia cite en exemple Java et sa compilation à la volée. Dans le cadre de cet article notre objectif est de charger une portion de code, stockée sous forme d'octet dans une variable de notre programme (section .data), directement en mémoire, puis de l'exécuter. Tout comme dans l'article précédent, la base de notre programme est une fenêtre (on ne peut plus basique), et notre but est de modifier son titre, voici le code de base:

.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
MyTitle     BYTE    "A great title",0
Secret      BYTE    "This is a secret title",0
ShellCode   BYTE    0C7h, 05h, 48h, 30h, 40h, 00h, 01Dh, 30h, 40h, 00h, 0C3h

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

.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 MyTitle

 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

Notez toutefois la variable nommée ShellCode, qui contient une liste d'instructions sous forme d'opcode (équivalent hexadécimal), et qui est le code que nous devons charger en mémoire. Tout d'abord, il faut savoir que sur tous les systèmes récents, un programme n'a pas les droits d'écriture n'importe ou dans la RAM, et doit demander l'autorisation pour pouvoir modifier ses droits sur une portion de mémoire (read-only, write, execute), sous Windows, la fonction qui permet de faire ces demandes au système se nomme VirtualProtect, notez que sa cousine : VirtualProtectEx permet de faire la même chose, mais pour n'importe quel exécutable chargé en mémoire.

Nous devons passer à cette fonction un pointeur vers l'adresse à partir de laquelle nous voulons modifier les droits, la taille de la région à modifier, les nouveaux droit souhaités (écriture, exécution, lecture) ainsi qu'un pointeur vers une variable afin de stocker l'état précédent de cette région en mémoire (pour restaurer dans son état initial).

Maintenant que nous savons comment récupérer les droits d'écriture sur une portion du programme, il nous manque tout de même un élément important un endroit où écrire notre ShellCode. La première idée consiste à remplir une portion de notre code avec du "Junk code", qui ne sera jamais exécuté mais qui nous permet de réserver une place en mémoire dans laquelle nous pourrons insérer notre ShellCode, il nous faut rajouter une nouvelle étiquette dans notre code:

toBeRewrited:
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop

NOP est une instruction tenant sur un byte (opcode : 0x90) qui ne fait absolument rien, nous avons donc maintenant, en mémoire, une place de quelques octets sur laquelle nous allons pouvoir écrire. Appellons la fonction VirtualProtect pour, par la suite y écrire notre ShellCode:

cld ; Clear direction flag (REP increases the EDI value by 1)
mov edi, offset toBeRewrited 
invoke VirtualProtect, edi, sizeof ShellCode, PAGE_READWRITE, addr ExPermission
mov esi, offset ShellCode
mov ecx, sizeof ShellCode
rep movsb ; Copy ECX bytes from ESI to EDI
call toBeRewrited ; Jump to our new code (and push eip)

Placez ce code juste avant l'étiquette startWindow. Pour résumer, ce bout de code nous donne les permissions d'écrire sur la mémoire à l'adresse de toBeRewrited, puis nous copions simplement, byte par byte, notre shell code à cette adresse. Explication du shellcode :

ShellCode   BYTE    0C7h, 05h, 48h, 30h, 40h, 00h, 01Dh, 30h, 40h, 00h, 0C3h

Ce shell code représente les instructions suivantes :

mov AppName, offset Secret
ret ; POP and JMP

Lancez l'application, et voilà : notre titre a été changé en "This is a secret title", notre code a donc bien été exécuté (notez par ailleurs qu'étant donné que nous écrivions dans une section de notre code qui était déjà exécutable, nous n'avons eu a demander que la permission PAGE_READWRITE à VirtualAlloc, afin de pouvoir y écrire). Cependant cette technique est loin d'être pratique car il faut écrire autant de "junk code" que la taille de notre ShellCode, et nous allons donc tenter de récupérer une place en mémoire au moment de l'exécution, ce qui peut être réalisé à l'aide de la fonction VirtualAlloc.

VirtualAlloc, donne moi un endroit ou écrire

Dans cette partie, nous reprenons un code "propre", celui donné au début de l'article, nous devons ajouter, avant l'étiquette startWindow, les instructions suivantes:

mov ecx, sizeof ShellCode
push ecx
invoke VirtualAlloc, NULL, ecx, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov edi, eax ; EAX contains the address of the allocated memory
pop ecx ; VirtualAlloc changes ecx
push edi ; Store the allocated address (needed for the CALL)
cld
mov esi, offset ShellCode ; Set source 
rep movsb ; Copy ECX byte from ESI to EDI
pop edi ; Restore the pointer to the allocated memory
call edi

Ce code ressemble fortement au précedent, si ce n'est que nous n'appellons pas VirtualProtect, mais VirtualAlloc, afin de récuperer un endroit ou écrire notre shellcode. Notez ici que l'on précise à VirtualAlloc, qu'en plus des droits de lecture et d'écriture, nous voulons également les droits d'exécution sur cette partie de la mémoire.

Et voilà nous en avons fini avec cet article sur les codes capable de se re-écrire (nous aurions très bien pu remplacer, au lancement une fonction par une autre, en utilisant la même méthode), ou encore décoder des instructions à certaines conditions (présence d'un debugger par exemple).

Une fois de plus, tous les codes utilisés dans cet article sont disponibles ici.

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.