L'ingénierie inverse

 · Temps de lecture: 5 mins

Introduction

Un programme simple écrit en C vérifie si un int donné est une clé valide.

Nous voulons déchiffrer ce programme, sans connaître le code

Cette façon de pirater s’appelle l’ingénierie inverse parce que nous essayons de déconstruire un programme pour révéler sa conception, son architecture ou pour extraire des connaissances de l’objet ; comme la recherche scientifique, la seule différence étant que la recherche scientifique concerne un phénomène naturel.

Testons ce programme avec quelques chiffres :

[email protected] ~/Téléchargements$ ./key 9
Résultat: faux
[email protected] ~/Téléchargements$ ./key 4
Résultat: faux
[email protected] ~/Téléchargements$ ./key 8
Résultat: faux
[email protected] ~/Téléchargements$

Pas de chance… Cela ne marche pas.

Nous allons ainsi faire de l’ingénierie inverse sur ce programme!

Assembly

Tout d’abord, je dois vous parler du code Assembly. Un langage d’assemblage (ou assembleur), souvent abrégé asm, est un langage de programmation de bas niveau dans lequel il existe une très forte correspondance entre les instructions du programme et les instructions du code machine de l’architecture.

Chaque langage d’assemblage est spécifique à une architecture informatique et à un système d’exploitation. En revanche, la plupart des langages de programmation de haut niveau sont généralement portables sur plusieurs architectures mais nécessitent une interprétation ou une compilation. Le langage d’assemblage peut aussi être appelé code machine symbolique.

Le langage d’assemblage comporte généralement une instruction par instruction machine, mais les directives d’assembleur, les macros et les étiquettes symboliques des emplacements de programme et de mémoire sont souvent également prises en charge.

Le code d’assemblage est converti en code machine exécutable par un programme utilitaire appelé assembleur. Le processus de conversion est appelé assemblage ou assemblage du code source.

C’est essentiellement une façon de visualiser les opérations exécutées par le processeur.

Débogueur

Un débogueur est un outil pour vous aider à déboguer votre code à l’exécution ou en le désassemblant. Démontage signifie convertir le programme compilé en code Assembly.

Les débogueurs les plus connus sont gdb et lldb. Nous allons utiliser lldb, mais le processus est généralement le même pour gdb.

Dans notre cas, la fonction main ressemble à cela lorsque l’on tape di -n main dans lldb:

[email protected] ~/Downloads$ lldb key
(lldb) target create "key"
Current executable set to 'key' (x86_64).
(lldb) di -n main
key[0x100000ec0] <+0>:   pushq  %rbp
key[0x100000ec1] <+1>:   movq   %rsp, %rbp
key[0x100000ec4] <+4>:   subq   $0x30, %rsp
key[0x100000ec8] <+8>:   movl   $0x0, -0x4(%rbp)
key[0x100000ecf] <+15>:  movl   %edi, -0x8(%rbp)
key[0x100000ed2] <+18>:  movq   %rsi, -0x10(%rbp)
key[0x100000ed6] <+22>:  cmpl   $0x1, -0x8(%rbp)
key[0x100000eda] <+26>:  jne    0x100000ef6               ; <+54>
key[0x100000ee0] <+32>:  leaq   0x9d(%rip), %rdi          ; "Please give a key to verify."
key[0x100000ee7] <+39>:  movb   $0x0, %al
key[0x100000ee9] <+41>:  callq  0x100000f58               ; symbol stub for: printf
key[0x100000eee] <+46>:  movl   %eax, -0x2c(%rbp)
key[0x100000ef1] <+49>:  jmp    0x100000f4a               ; <+138>
key[0x100000ef6] <+54>:  movq   -0x10(%rbp), %rax
key[0x100000efa] <+58>:  movq   0x8(%rax), %rax
key[0x100000efe] <+62>:  movq   %rax, -0x18(%rbp)
key[0x100000f02] <+66>:  movq   -0x18(%rbp), %rdi
key[0x100000f06] <+70>:  callq  0x100000f52               ; symbol stub for: atoi
key[0x100000f0b] <+75>:  movl   %eax, -0x1c(%rbp)
key[0x100000f0e] <+78>:  movl   -0x1c(%rbp), %edi
key[0x100000f11] <+81>:  callq  0x100000e10               ; is_prime
key[0x100000f16] <+86>:  leaq   0x8f(%rip), %rdi          ; "Résultat: %s\n"
key[0x100000f1d] <+93>:  leaq   0x82(%rip), %rcx          ; "Faux"
key[0x100000f24] <+100>: leaq   0x76(%rip), %rdx          ; "Vrai"
key[0x100000f2b] <+107>: movl   %eax, -0x20(%rbp)
key[0x100000f2e] <+110>: movl   -0x20(%rbp), %eax
key[0x100000f31] <+113>: cmpl   $0x1, %eax
key[0x100000f34] <+116>: cmoveq %rdx, %rcx
key[0x100000f38] <+120>: movq   %rcx, -0x28(%rbp)
key[0x100000f3c] <+124>: movq   -0x28(%rbp), %rsi
key[0x100000f40] <+128>: movb   $0x0, %al
key[0x100000f42] <+130>: callq  0x100000f58               ; symbol stub for: printf
key[0x100000f47] <+135>: movl   %eax, -0x30(%rbp)
key[0x100000f4a] <+138>: xorl   %eax, %eax
key[0x100000f4c] <+140>: addq   $0x30, %rsp
key[0x100000f50] <+144>: popq   %rbp
key[0x100000f51] <+145>: retq

Un peu moche, n’est-ce pas?

Espérons qu’il y ait quelques commentaires à droite qui nous donneraient un indice sur ce que fait ce programme.

Voyez-vous la fonction is_prime appelée à 0x100000f11?

Cela signifie que la clé a à voir avec les nombres premiers, donc vérifions avec 23!

(lldb) run 23
Process 7577 launched: '/Users/user/Downloads/key' (x86_64)
Résultat: Vrai
Process 7577 exited with status = 0 (0x00000000)

Et ça marche!

Désassembleur d’interface graphique

Ok, donc c’était un peu complexe, même pour un programme très simple. Espérons qu’il existe un débogueur GUI comme Hopper qui nous aidera à convertir Assembly en code C: Reconstruction de la fonction principale par Hopper la fonction main représentée

Comme on peut le voir, c’est assez lisible et ressemble à la fonction main d’origine. Nous pouvons faire la même chose avec la fonction `is_prime’, donc nous pourrions théoriquement reconstruire le programme.

Vrai programe

Comme je l’ai dit plus tôt, j’ai créé ce programme, alors le voici :

#include<stdio.h>
#include <stdlib.h>

int is_prime(int num) {
     if (num <= 1) return 0;
     if (num % 2 == 0 && num > 2) return 0;
     for(int i = 3; i < num / 2; i+= 2) {
         if (num % i == 0)
             return 0;
     }
     return 1;
}

int main(int argc, char *argv[]) {
	typedef enum { false, true } bool;
	if (argc == 1) {
		printf("Please give a key to verify.");
	} else {
		char *key = argv[1];
		int k = atoi(key);
		int p = is_prime(k);
		char *r = p == 1 ? "Vrai" : "Faux";
		printf("Résultatsult: %s\n", r);
	}
	return 0;
}

Le désassembleur était très proche. Maintenant, vous savez que la ingénierie inverse, c’est de la reconstruction. __