Explication de la technique
En ouvrant un éxécutable avec un éditeur hexadécimal tel hexedit, on peut avoir le bytecode de notre programme. Autrement dit, le code complet en hexadécimal ou en décimal (ce qui donne les caractères que l'on peut voir en ouvrant un éxécutable avec un éditeur texte, qui est la correspondance ascii du bytecode). Le code complet n'étant qu'une succession de nombres (les opcodes), il nous est tout à fait possible d'en faire la somme. Le principe de la protection par code checksum est de vérifier que la somme de ces opcodes n'a pas été modifiée, autrement dit, que le programme tourne avec son code original. Pour ce, nous allons, à l'aide d'un programme simple en assembleur, tout d'abord vous montrer comment il est possible de contourner facilement des instructions de comparaison (cmp, cmpl) puis comment insérer un code de vérification de la somme des opcodes.
Exemple
Nous allons étudier le programme suivant, toujours codé en assembleur AT&T pour Unix. Il n'est autre qu'un classique Hello, World, légèrement modifié pour illustrer notre point et également codé en version longue (d'une part pour compliquer un peu le code et décourager les crackers débutants, c'est une bonne habitude à prendre, et d'autre part car les instructions telles xor ou inc sont bien plus rapides que mov, mais ce sont des détails ;-) ) :
- .data #declaration du segment des variables statiques initialisées
bonjour: .string "Hello, World !\n"
non_affiche: .string "Ce message ne peut pas être affiché\n"
.text #declaration du segment code
.global _start
_start:
xorl %eax,%eax #Affichage de Hello, World !
movb $4, %al
xorl %ebx,%ebx
inc %ebx
movl $bonjour,%ecx
xorl %edx,%edx
mov $15,%edx
int $0x80
xorl %eax,%eax #On mets eax à 0, puis on compare 1 et al, ce qui est donc toujours faux
cmp $1,%al
jne exit #Ce Jump if Not Equal sera donc en théorie toujours réalisé
naffiche: #Affiche Ce message ne peut pas être affiché
xorl %eax,%eax
movb $4, %al
xorl %ebx,%ebx
inc %ebx
movl $non_affiche,%ecx
xorl %edx,%edx
mov $36,%edx
int $0x80
exit: #sortie
xorl %eax,%eax
xorl %ebx,%ebx
inc %eax
int $0x80
- exit: #sortie
mov $1,%eax
mov $0,%ebx
int $0x80
- $ gcc test.s -c -o test.o && ld test.o && ./a.out
Hello, World !
$
- 3C 01 (cmp $1,%al)
75 15 (jne +15 <exit>)
- 3C 00 (cmp $0,%al)
75 15 (jne +15 <exit>)
- 3C 01 (cmp $1,%al)
74 15 (je +15 <exit>)
- $ hexedit a.out
$./a.out
Hello, World !
Ce message ne peut pas être affiché
$
- .data #declaration du segment des variables statiques initialisées
bonjour: .string "Hello, World !\n"
non_affiche: .string "Ce message ne peut pas être affiché\n"
tentative_crack: .string "Tentative de crack !\nAbandon...\n"
.text #declaration du segment code
.global _start
_start:
jmp checksum #On commence par effectuer Checksum
suite: #on revient ici après le checksum s'il est positif
xorl %eax,%eax
movb $4, %al
xorl %ebx,%ebx
inc %ebx
movl $bonjour,%ecx
xorl %edx,%edx
mov $15,%edx
int $0x80
xorl %eax,%eax
cmp $1,%al
jne exit
naffiche:
xorl %eax,%eax
movb $4, %al
xorl %ebx,%ebx
inc %ebx
movl $non_affiche,%ecx
xorl %edx,%edx
mov $36,%edx
int $0x80
exit:
xorl %eax,%eax
xorl %ebx,%ebx
inc %eax
int $0x80
checksum: #fonction checksum
xorl %ebx,%ebx
mov $checksum,%ecx
sub $_start,%ecx
mov $_start,%esi
boucle: #boucle d'addition des opcodes
lodsb
add %eax,%ebx
loop boucle
cmpl $5917,%ebx #on a au préalable compté les opcodes et trouvé 5917
jne crack #Si le résultat de la boucle n'est pas 5917, on passe à <crack>
jmp suite #sinon on revient au début du programme
crack: #On avertit de la tentative de crack et on quitte
xorl %eax,%eax
movb $4, %al
xorl %ebx,%ebx
inc %ebx
movl $tentative_crack,%ecx
xorl %edx,%edx
mov $32,%edx
int $0x80
jmp exit
- $ gcc checksum.s -c -o checksum.o && ld checksum.o -o checksum && ./checksum
Hello, World !
$ hexedit checksum
$ ./checksum
Tentative de crack !
Abandon...
$
0 commentaires:
Enregistrer un commentaire