Ces points d’arrêt sont associés au signal SIGTRAP. Quand un débuggueur lit un caractère 0xCC, il mets le programme en pause (ce qui permet de l’analyser à l’instant X). Quand un programme reçoit un SIGTRAP, il effectue l’action associée au signal (par défaut, il quitte). Le principe de cette méthode est simple : on modifie l’action effectuée par un SIGTRAP. Donc, un programme éxécuté normalement va suivre cette action, alors qu’un programme en cours de debug va se mettre en pause, puis continuer (et être piégé dans une partie du programme qui lui sera bien sûr réservée).
Illustration
Voici un exemple d’utilisation des faux breakpoints. Le programme suivant est le même que celui de la partie ptrace, légèrement modifié :
auth.cpp : Exemple de l’utilisation d’un faux breakpoint
- #include <signal.h>
- #include <iostream>
- using std::cout;
- using std::cin;
- using std::endl;
- #include <string>
- using std::string;
- #define MDP "exemple_pass"
- void authentification(int signo) {
- string pass;
- cout << "Authentification requise\nMot de passe :\t";
- cin >> pass; //Capture clavier
- if (pass == MDP) //Si la chaîne rentrée au clavier est pareil que la chaîne MDP définie plus tôt
- cout << "Authentification réussie, bienvenue dans la suite du programme" << endl;
- else cout << "Echec de l'authentification\nAbandon..." << endl; //Sinon
- }
- int main() {
- signal(SIGTRAP, authentification); //réception de SIGTRAP => authentification()
- __asm__("int3"); //On pose un breakpoint qui va envoyer un SIGTRAP
- return 1; //On quitte en état d'erreur
- }
Le return 1 ; va juste nous permettre de prouver que le débugger a terminé dans cette partie du programme, contrairement à ce qui se passe en temps normal. En réalité, on utilise souvent ce genre de piège pour faire un faux clône de la suite réell du programme. Le cracker va essayer de la cracker sans comprendre pourquoi le vrai programme ne se soumet pas à ses ordres. Cette technique s’inscrit plutôt dans une optique de découragement du reverser. Voici un exemple d’utilisation du programme puis ce qui se passe en utilisant gdb :
$ g++ auth.cpp -o auth && ./auth
Authentification requise
Mot de passe : testdepass
Echec de l'authentification
Abandon...
$ ./auth
Authentification requise
Mot de passe : exemple_pass
Authentification réussie, bienvenue dans la suite du programme
$ gdb -q auth
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) r
Starting program: /home/SeriousHack/auth
Failed to read a valid object file image from memory.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x0804894c in main ()
(gdb) c
Continuing.
Program exited with code 01.
Comme prévu, bien que tout marche parfaitement en utilisation normale, le programme quitte tout de suite avec un code de sortie 01, ce qui correspond à ce que nous avons codé. Cette protection étant aisément contournable, on prend l’habitude de la protéger par faux désassemblage et par checksum, ce que nous nous proposons d’étudier maintenant.