Techniques anti-debugging et protection logicielle
II°) Le faux désassemblage (false disassembly)
Explication de la technique
   Encore une fois, le principe de cette technique est plutôt simple (il suffisait d'y penser, comme 
qui dirait..). Le but est de place des chaines de caractères dans le programme en assembleur ayant la valeur d'opcodes 
ainsi, le debuggeur trouvant ces opcodes va les prendre en compte. En plaçant des caractères bien étudiés, on peut 
totalement brouiller un code désassemblé ou juste les parties que l'on ne veut pas montrer. L'illustration de ce procédé 
devrait rendre les choses limpides.
Illustration
   Intéressons-nous au code largement commenté qui suit. Ce code est en langage assembleur, synthaxe 
AT&T pour Unix. Pour ceux qui ne connaissent pas l'assembleur, il est aisé de le comprendre après avoir lu la partie 
mémoire de ce site : tout d'abord, les segments data et bss sont remplis si besoin est, puis le segment text. Ensuite, vous n'avez qu'une suite d'appels systèmes qui se déroulent de la même 
façon, à savoir, on entre dans le registre %eax le numéro de l'appel système, puis dans les registres suivants les 
variables nécessaires à l'appel (placés dans la pile), puis 0x80 qui correspond à l'appel au kernel qui va entraîner l'éxécution du syscall. 
Vous avez accès à l'ensemble des appels systèmes depuis /usr/include/asm-i386/unistd.h et à leurs paramètres dans 
les pages du manuel correspondantes. Intéressons nous maintenant à ce programme d'authentification :
.data     #declaration des variables statiques initialisées
  auth_req: .string "Authentification requise\nMot de passe :\t"
 
  ok: .string "Authentification OK\n"
   
  mauvais: .string "Echec de l'authentification\nAbandon...\n"
.text     #declaration du code
  .global _start
   _start:
  
     mov $4, %eax   #Afficher le message auth_req
     mov $1,%ebx   #1 est le flux STDOUT (votre écran)
     mov $auth_req,%ecx   #On mets le message auth_req dans la pile
     mov $40,%edx   #40 caractères à afficher
     int $0x80   #On effectue le syscall 4
  
     mov $3,%eax   #Lire la réponse au clavier
     mov $0,%ebx   #0 est le flux STDIN (clavier)
     movl %esp,%ecx   #%ecx pointe vers le haut de la pile (donc vers ce qui sera entré qui sera en haut de la pile après le syscall)
     mov $10,%edx   #On lit 10 caractères max
     int $0x80   #on effectue le syscall 3
  
     cmpl $0x37333331,(%ecx)   #On compare ce qui a été entré avec 0x37333331 qui est 7331 en héxadécimal, interprété 1337 en mémoire (little endian)
     jne echec   #Si ce n'est pas égal, on passe au label echec
     je debut   #sinon au label debut
   exit:
     mov $1, %eax   #Quitter
     mov $0, %ebx   #Code de sortie (ici 0, pas d'erreur)
     int $0x80   #Appel au syscall 1
   debut:
     mov $4, %eax   #Afficher le message ok
     mov $1,%ebx
     movl $ok,%ecx
     mov $20,%edx
     int $0x80
     jmp exit   #On passe au label exit
   echec:
     mov $4, %eax   #Afficher le message mauvais
     mov $1,%ebx
     movl $mauvais,%ecx
     mov $39,%edx
     int $0x80
     jmp exit
Cet exemple d'une authentification archaïque est simple : il affiche un prompt à l'écran demandant le mot de passe, 
prend 10 caractères et vérifie s'il s'agit du bon mot de passe ou non (ici, 1337). Ensuite, il affiche à l'écran 
le résultat de l'authentification. Même s'il ne faut jamais vérifier les mots de passe de cette façon, notre but 
ici est plus de montrer comment arriver à cacher les points sensibles d'un programme au désassemblage. Tout d'abord, 
essayons le programme et essayons de le désassembler à l'aide de gdb :
$ gcc auth.s -c -o auth.o && ld auth.o -o auth && ./auth
Authentification requise
Mot de passe :  test-pass
Echec de l'authentification
Abandon...
$ ./auth
Authentification requise
Mot de passe :  1337
Authentification OK
$ gdb -q auth
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disas _start
Dump of assembler code for function _start:
0x08048074 <_start+0>:  mov    $0x4,%eax
0x08048079 <_start+5>:  mov    $0x1,%ebx
0x0804807e <_start+10>: mov    $0x80490e0,%ecx
0x08048083 <_start+15>: mov    $0x28,%edx
0x08048088 <_start+20>: int    $0x80
0x0804808a <_start+22>: mov    $0x3,%eax
0x0804808f <_start+27>: mov    $0x0,%ebx
0x08048094 <_start+32>: mov    %esp,%ecx
0x08048096 <_start+34>: mov    $0xa,%edx
0x0804809b <_start+39>: int    $0x80
0x0804809d <_start+41>: cmpl   $0x37333331,(%ecx)
0x080480a3 <_start+47>: jne    0x80480c6 <echec>
0x080480a5 <_start+49>: je     0x80480ae <debut>
End of assembler dump.
(gdb)
On a donc compilé et linké le programme et il semble marcher. Ensuite, on a désassemblé le label _start avec gdb. 
Evidemment, le pass apparaît en clair, puisque nous l'avions laissé en clair dans le programme :
0x0804809d <_start+41>: cmpl   $0x37333331,(%ecx)
Maintenant, nous allons ajouter dans le code de l'assembleur l'instruction .ascii "\xeb\x01\xe8" juste 
avant l'instruction cmpl et observer comment le désassembleur de gdb va l'interpréter :
$ gcc auth.s -c -o auth.o && ld auth.o -o auth && ./auth
Authentification requise
Mot de passe :  1337
Authentification OK
$ ./auth
Authentification requise
Mot de passe :  retest
Echec de l'authentification
Abandon...
$ gdb -q auth
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disas _start
Dump of assembler code for function _start:
0x08048074 lt;_start+0gt;:  mov    $0x4,%eax
0x08048079 lt;_start+5gt;:  mov    $0x1,%ebx
0x0804807e lt;_start+10gt;: mov    $0x80490e4,%ecx
0x08048083 lt;_start+15gt;: mov    $0x28,%edx
0x08048088 lt;_start+20gt;: int    $0x80
0x0804808a lt;_start+22gt;: mov    $0x3,%eax
0x0804808f lt;_start+27gt;: mov    $0x0,%ebx
0x08048094 lt;_start+32gt;: mov    %esp,%ecx
0x08048096 lt;_start+34gt;: mov    $0xa,%edx
0x0804809b lt;_start+39gt;: int    $0x80
0x0804809d lt;_start+41gt;: jmp    0x80480a0 <_start+44>
0x0804809f lt;_start+43gt;: call   0x3b35ba25
0x080480a4 lt;_start+48gt;: xor    (%edi),%esi
0x080480a6 lt;_start+50gt;: jne    0x80480c9 <echec>
0x080480a8 lt;_start+52gt;: je     0x80480b1 <debut>
End of assembler dump.
(gdb)
Effectivement, malgré le bon fonctionnement du programme, à partir de _start + 41, rien ne va plus dans ce 
désassemblage !
En fait, le résultat est facilement explicable : le désassembleur a interprété la chaîne que 
l'on a déclaré comme des opcodes. Or, \xEB est l'équivalent en hexadecimal de l'instruction jmp, le \x01 qui le 
suit indique donc qu'il faut faire un jmp d'un octet et \xE8 est le début d'un call (qui appelle une fonction). 
Par conséquent, on a désaligné le code désassemblé qui va afficher un jmp +1 puis un call avec les 4 prochaines 
bytes qu'il va trouver et continuer avec des instructions sans sens jusqu'à retomber sur ses pattes (c'est à dire jusqu'à 
retrouver l'alignement des réelles instructions, ici, trois lignes plus tard avec le jne).
Cette technique est fréquemment utilisée pour cacher les sauts aux fonctions checksum ou les vérifications des 
appels ptrace. Elle peut aussi être utilisée pour brouiller d'autres parties du code et le rendre illisible, ce 
qui complique vraiment la tâche de l'éventuel cracker.