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.