Contourner les protections anti-désassembleurs

Written by aaSSfxxx -

Dans le monde du reverse-engineering, c'est-à-dire l'art d'analyser le fonctionnement d'un binaire dans notre cas, on peut trouver des protections différentes, allant du chiffrage du programme (ou de sa compression) à la détection de débuggeurs (outils servant à contrôler le flux d'exécution d'un programme, l'arrêter à certains endroits). Une autre technique, qui sera celle montrée ici est le fait de rendre les désassembleurs incapables de retrouver le code assembleur d'un binaire.

Nous allons donc voir ici comment analyser ce genre de programmes, et comment contourner cette protection à travers un exemple simple pour commencer.

Prérequis:

Savoir manier un éditeur hexadécimal, connaître les instructions assembleur de base, avoir quelques notions en cracking/reverse-engineering.

Premier contact avec le crackme

Tout d'abord, vous pouvez télécharger le crackme à cette adresse: http://repos.aassfxxx.infos.sr/misc/crackme/demo . Une fois ceci fait, nous allons prendre notre désassembleur favori (IDA Pro dans mon cas, mais objdump fait aussi l'affaire). Lorsqu'on essaie de l'exécuter, le crackme nous accueille avec un gentil message "Essaie encore :þ", ce qui montre que le crackme fonctionne. Un "file" sur le crackme nous indique qu'il est stripped, c'est-à-dire que tous les symboles de débuggage ont été retirés du binaire. Ainsi, le désassembleur va nous diriger sur _start (le "vrai" point d'entrée du programme, et non main, appelé par _start).

On désassemble donc notre programme, comme on ferait pour n'importe quel autre crackme. Il va donc falloir retrouver notre "main", étant donné que le désassembleur n'a pas pu le localiser. On s'aperçoit qu'il y a un push offset sub_8048495 avant un call __libc_start_main, ce qui nous indique que notre main est à l'adresse 0x08048495. Lorsqu'on désassemble ce main, on s'aperçoit qu'il y a plein de code qui n'a a priori aucun sens (ou pire dans le cas d'IDA, une suite de dwords incompréhensibles lorsqu'on est en "Text View"). Nous allons donc voir ici comment pallier ce problème.

Analyse statique du binaire

On va ici se servir du désassembleur pour comprendre comment le processeur arrive à exécuter cette bouillie d'instructions sans broncher. Tout d'abord, on commence par trouver le "main" (dans notre cas à 0x08048495). Si vous ne trouvez rien à cette adresse (mais que les chiffres à la fin sont proches), c'est qu'il y a déjà des protections contre le désassemblage (et une analyse dynamique conviendra mieux pour ce cas). Ce n'est pas le cas dans ce crackme. On va donc analyser les premières instructions du programme:

push    ebp
mov     ebp, esp
mov     eax, offset dword_80484A4
mov     edx, 2
jmp     short loc_8048516

On aperçoit ici le prologue habituel ("push ebp" et "mov ebp,esp"), l'initialisation de deux registres puis un saut vers 0x8048516. Regardons donc cette fonction:

add     eax, edx
xor     edx, edx
mov     edx, eax
sub     edx, offset dword_80484A4
add     eax, edx
jmp     eax

La première chose qui nous frappe, c'est ce "jmp eax" : on sait que notre programme va aller sauter à la valeur contenue dans eax. Or, eax est initialisé avec "0x080484A4" qui ressemble curieusement à une adresse de fonction et qu'on retrouve dans notre désassemblage (là où le code est devenu incohérent).

Le programme ajoute aussi edx à la valeur d'eax, or edx vaut 2. On a donc, eax = 0x080484A4 + 2. Ensuite, on met eax dans edx, puis on retranche la valeur de la "fonction" à edx, qui est du coup égal à 2. On ajoute encore edx (donc 2) à eax, avant de jmp à la valeur contenue dans eax. On a donc 4 octets de décalage entre la fonction annoncée et la fonction "réelle" qui contient n'importe quoi, ce qui embrouille le désassembleur. On va donc nopper ces 4 octets afin de redonner du sens à notre code (je supposerai ici que vous savez localiser et éditer des octets dans un binaire). Sous IDA, il suffira juste de se placer 4 octets après "0x080484A4" et de presser "C", ce qui demandera à IDA d'analyser le code.

Conclusion

Nous avons vu ici comment contourner une protection "basique", et le programme analysé est bien évidemment élémentaire. Cependant, ce genre de procédés est aussi couplé avec d'autres techniques telles que les anti-débuggeurs ou la compression du code, ce qui rend l'analyse plus difficile.

J'espère malgré tout que cet article vous aura ouvert de nouvelles possibilités, et qui sait, trouver une méthode pour résoudre un problème plus délicat.

That's all folks !