Le livre «Hacking: The Art of Exploit. 2e éd. "

image Chaque programmeur est essentiellement un hacker. Après tout, le piratage s'appelait initialement la recherche d'une solution habile et non évidente. La compréhension de la programmation vous aide à trouver les vulnérabilités et les compétences de détection des vulnérabilités vous aident à créer des programmes, de nombreux pirates font les deux en même temps. Il existe des mouvements non standard intéressants à la fois dans les techniques d'écriture de programmes élégants et dans les techniques de recherche de points faibles.

Par où commencer? Pour écraser la mémoire à l'aide de débordements de tampon, accéder à un serveur distant et intercepter les connexions, vous devez programmer en C et assembleur, utiliser le code shell et les registres de processeur, vous familiariser avec les interactions réseau et le cryptage, et bien plus encore.

Peu importe à quel point nous voulons croire aux miracles, les logiciels et les réseaux informatiques dont dépendent notre vie quotidienne présentent des vulnérabilités.

Un monde sans hackers est un monde sans curiosité et sans solutions innovantes. (John Erickson)

Contre-mesures


Il y a une telle grenouille - une terrible listolaz (Phyllobates terribilis). Ses glandes cutanées contiennent le poison le plus puissant. Il suffit de le toucher pour obtenir un empoisonnement fatal. Un remède aussi puissant s'explique par le fait que ces grenouilles se nourrissent de certains types de serpents qui ont développé une immunité au poison. Progressivement, le poison des grenouilles est devenu de plus en plus fort. À la suite d'une telle évolution conjointe, les terribles grimpeurs de feuilles n'ont plus d'autres ennemis naturels. Quelque chose de similaire se produit avec les pirates. Les techniques qu'ils ont inventées sont connues depuis longtemps, donc l'émergence de contre-mesures est assez naturelle. En réponse, les pirates cherchent des moyens de contourner et de détruire les mécanismes de défense, ce qui conduit à la création de nouvelles techniques défensives.

Ce cycle de recherche de mesures et de contre-mesures est très utile. Les virus et les vers deviennent la cause de nombreux problèmes et entraînent de lourdes pertes pour les entreprises, mais en même temps, ils forcent les développeurs à prendre des mesures de rétorsion pour résoudre les problèmes qui sont survenus. Les vers s'auto-répliquent à l'aide de vulnérabilités logicielles de faible qualité. Les erreurs passent souvent inaperçues au fil des ans, et des vers relativement inoffensifs tels que CodeRed ou Sasser forcent les développeurs à les corriger. Ce processus peut être comparé à la varicelle, qui est préférable pour tomber malade dans l'enfance, et non à l'âge adulte, lorsqu'elle peut entraîner des conséquences désastreuses. Si les vers Internet n'avaient pas attiré l'attention universelle sur les failles de sécurité, ces failles resteraient ouvertes à des attaques à des fins bien plus malveillantes que la simple auto-reproduction. De cette façon, les vers et les virus contribuent à la sécurité à long terme. Mais il existe des méthodes plus actives: des techniques qui tentent d'annuler les résultats d'une attaque ou de la rendre complètement impossible. Le concept de «contre-mesures» est assez vague, ces mots peuvent signifier des moyens techniques de sécurité, un ensemble de règles, un programme ou tout simplement un administrateur système attentif. Les contre-mesures sont conditionnellement divisées en deux groupes: essayer de détecter une attaque et essayer de protéger la vulnérabilité.

Outil de détection d'attaque 0x610


Les contre-mesures du premier groupe se résument à des tentatives de remarquer l'intrusion dans le temps et d'y réagir d'une manière ou d'une autre. Le processus de reconnaissance peut être mis en œuvre de n'importe quelle manière - de l'administrateur lisant les journaux système aux programmes analysant le trafic réseau. Parfois, la réaction à une invasion revient à déconnecter automatiquement la connexion ou à fermer le processus, et parfois l'administrateur doit étudier attentivement tout le contenu de la console.

Les méthodes connues d'exploitation des vulnérabilités ne sont pas aussi dangereuses pour l'administrateur système que celles qu'il ne connaît pas encore. Plus tôt une intrusion peut être détectée, plus le travail commencera tôt et plus elle sera localisée. Une intrusion ignorée depuis des mois est un grave sujet de préoccupation.

Pour reconnaître l'intrusion, vous devez anticiper ce que l'attaquant va faire. Cela fournit des informations sur ce qui doit être surveillé exactement. Les détecteurs recherchent des modèles d'attaque familiers dans les journaux, les paquets réseau et même dans la mémoire du programme. Lorsqu'une intrusion est détectée, vous pouvez priver le pirate de l'accès à la machine, restaurer le système de fichiers endommagé à l'aide d'une sauvegarde et identifier et corriger le trou de sécurité. Avec les capacités de sauvegarde et de restauration disponibles aujourd'hui, les contre-mesures qui se réduisent à la détection des attaques sont assez efficaces.

Pour un attaquant, la détection signifie contrer tout ce qu'il fait. Bien sûr, il est loin d'être toujours possible de remarquer immédiatement une attaque, il existe donc un certain nombre de scénarios «grab and run» dans lesquels le fait de la détection n'est pas important, mais même dans ces cas, il vaut mieux ne pas laisser de traces. Le secret est l'une des qualités les plus précieuses des pirates. L'accès au shell de commandes avec des droits d'administrateur en exploitant les vulnérabilités permet de faire quoi que ce soit sur le système, et si vous parvenez à éviter la détection, personne ne sera au courant de votre présence. C'est la combinaison de la permissivité et de l'invisibilité qui rend les pirates informatiques dangereux. Ils peuvent facilement intercepter les mots de passe et les données sur le réseau, ajouter des signets aux programmes pour un accès non autorisé ultérieur et attaquer d'autres nœuds du réseau. Pour rester dans l'ombre, le pirate doit comprendre quelles méthodes de reconnaissance sont utilisées dans un cas particulier. Si vous savez exactement ce qu'ils recherchent, vous pouvez éviter certains modèles d'exploitation de la vulnérabilité ou masquer vos actions comme valides. Le facteur moteur du cycle d'évolution conjointe des outils et des techniques de détection qui permettent de passer inaperçu sont des idées qui ne sont pas encore apparues de l'autre côté.

Démons système 0x620


Cependant, les contre-mesures contre les pirates et leurs solutions de contournement sont mieux discutées à l'aide d'un exemple pratique. Nous allons maintenant considérer une attaque contre un programme serveur qui accepte les connexions entrantes. Sur les systèmes d'exploitation de type UNIX, les démons système conviennent à ces critères. Un démon est un programme fonctionnant en arrière-plan et d'une certaine manière séparé du terminal de contrôle. Le terme a été inventé dans les années 1960 par des pirates du Massachusetts Institute of Technology. Le prototype était une créature mythique triant les molécules d'une expérience mentale du physicien James Maxwell. Le démon Maxwell possédait une capacité surnaturelle pour effectuer facilement des tâches complexes, violant la deuxième loi de la thermodynamique. De même, les démons système Linux exécutent inlassablement des tâches telles que l'octroi de l'accès à SSH et la maintenance des journaux système. Les noms de démon se terminent généralement par d, ce qui souligne leur nature: par exemple, sshd ou syslogd.

Une petite modification transformera tinyweb.c de la section 0x427 en une ressemblance réaliste avec un démon système. La nouvelle version du code contient la fonction daemon (), qui engendre un nouveau processus d'arrière-plan. Il est utilisé par de nombreux processus de démon système Linux. Voici la page qui lui est dédiée dans l'annuaire:

DAEMON (3) Référence du programmeur Linux DAEMON (3)

NOM
démon - exécuté en arrière-plan
Syntaxe

#include <unistd.h> int daemon(int nochdir, int noclose); 

DESCRIPTION

La fonction daemon () déconnecte le programme du terminal de commande et démarre
elle en arrière-plan en tant que démon système.
Avec un argument différent de zéro, nochdir, daemon () change le répertoire de travail courant
à la racine ("/").
Avec nullose, la fonction daemon () redirige les threads
entrée standard, sortie standard et erreurs dans / dev / null.
VALEUR DE RETOUR

(Cette fonction génère une copie du processus, et si fork () réussit
le parent exécute _exit (0) de sorte que d'autres erreurs ne soient visibles que
processus enfant.) En cas de succès, renvoie zéro. En cas d'erreur, la fonction
daemon () renvoie -1 et assigne le numéro d'erreur à la variable globale errno
à partir de la bibliothèque de fonctions fork (2) et setsid (2).

Les démons système n'ont pas de terminal de contrôle, donc le code du nouveau démon tinywebd sera affiché dans le journal de bord. Les démons sont généralement contrôlés par des signaux. La nouvelle version de tinyweb devrait pouvoir recevoir un signal d'achèvement afin de sortir correctement.

Présentation du signal 0x621


Les signaux fournissent une communication interprocessus sous UNIX. Une fois qu'un signal est reçu par un processus, le système d'exploitation interrompt son exécution pour appeler le gestionnaire de signaux. Chaque signal a son propre numéro et son propre gestionnaire. Par exemple, lorsque vous appuyez sur la combinaison de touches Ctrl + C, un signal d'interruption est envoyé, dont le gestionnaire termine le programme ouvert sur le terminal de commande, même s'il est entré dans une boucle infinie.

Vous pouvez créer vos propres gestionnaires de signaux et les enregistrer à l'aide de la fonction signal (). Regardons un code dans lequel plusieurs gestionnaires sont enregistrés pour certains signaux, et dans la partie principale il y a une boucle infinie.

 signal_example.c #include <stdio.h> #include <stdlib.h> #include <signal.h> /*    signal.h * #define SIGHUP 1  * #define SIGINT 2  (Ctrl+C) * #define SIGQUIT 3  (Ctrl+\) * #define SIGILL 4   * #define SIGTRAP 5   * #define SIGABRT 6  * #define SIGBUS 7   * #define SIGFPE 8     * #define SIGKILL 9   * #define SIGUSR1 10   1 * #define SIGSEGV 11   * #define SIGUSR2 12   2 * #define SIGPIPE 13    ,    * #define SIGALRM 14   ,  alarm() * #define SIGTERM 15  (  kill) * #define SIGCHLD 17    * #define SIGCONT 18 ,    * #define SIGSTOP 19  (  ) * #define SIGTSTP 20   [] (Ctrl+Z) * #define SIGTTIN 21       * #define SIGTTOU 22       */ /*   */ void signal_handler(int signal) { printf("  %d\t", signal); if (signal == SIGTSTP) printf("SIGTSTP (Ctrl-Z)"); else if (signal == SIGQUIT) printf("SIGQUIT (Ctrl-\\)"); else if (signal == SIGUSR1) printf("SIGUSR1"); else if (signal == SIGUSR2) printf("SIGUSR2"); printf("\n"); } void sigint_handler(int x) { printf(" Ctrl-C (SIGINT)   \nExiting.\n"); exit(0); } int main() { /* Registering signal handlers */ signal(SIGQUIT, signal_handler); //  signal_handler() signal(SIGTSTP, signal_handler); //    signal(SIGUSR1, signal_handler); signal(SIGUSR2, signal_handler); signal(SIGINT, sigint_handler); //  sigint_handler()  SIGINT while(1) {} //   } 

Lorsqu'un programme compilé est exécuté, les gestionnaires de signaux sont enregistrés, puis le programme entre dans une boucle sans fin. Mais, malgré cela, les signaux entrants interrompent son exécution et se tournent vers les gestionnaires enregistrés. Voici des exemples d'application de signaux pouvant être activés à partir du terminal de commande. Une fois la fonction signal_handler () terminée, le contrôle retourne à la boucle interrompue, tandis que la fonction sigint_handler () termine le programme.

 reader@hacking:~/booksrc $ gcc -o signal_example signal_example.c reader@hacking:~/booksrc $ ./signal_example   20 SIGTSTP (Ctrl-Z)   3 SIGQUIT (Ctrl-\)  Ctrl-C (SIGINT)    Exiting. reader@hacking:~/booksrc $ 

La commande kill vous permet d'envoyer un ensemble complet de signaux à un processus. Par défaut, il envoie un signal d'achèvement (SIGTERM). L'ajout de l'option -l affiche une liste de tous les signaux possibles. Voyons comment le programme signal_example exécuté sur un autre terminal envoie les signaux SIGUSR1 et SIGUSR2.

 reader@hacking:~/booksrc $ kill -l 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2 13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT 17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP 21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU 25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH 29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN 35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4 39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8 43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6 59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2 63) SIGRTMAX-1  64) SIGRTMAX reader@hacking:~/booksrc $ ps a | grep signal_example 24491 pts/3    R+     0:17 ./signal_example 24512 pts/1    S+     0:00 grep signal_example reader@hacking:~/booksrc $ kill -10 24491 reader@hacking:~/booksrc $ kill -12 24491 reader@hacking:~/booksrc $ kill -9 24491 reader@hacking:~/booksrc $ 

À la fin, la commande kill -9 envoie un signal SIGKILL. Le gestionnaire de signaux ne peut pas être modifié, donc la commande kill -9 est toujours utilisée pour tuer les processus. Le programme signal_example lancé sur un autre terminal montre que les signaux ont été interceptés et que le processus a été détruit.

 reader@hacking:~/booksrc $ ./signal_example   10       SIGUSR1   12       SIGUSR2 Killed reader@hacking:~/booksrc $ 

Les signaux eux-mêmes sont très simples, mais l'interaction entre les processus peut rapidement se transformer en un réseau complexe de dépendances. Heureusement, dans notre démon tinyweb, les signaux sont utilisés uniquement pour l'arrêt correct, et tout est mis en œuvre très simplement.

Démon Tinyweb 0x622


La nouvelle version de tinywebd est un démon système lancé en arrière-plan sans terminal de contrôle. Les données de sortie sont écrites dans le journal de bord avec des horodatages et le programme lui-même attend que le signal SIGTERM termine correctement le travail.

Les modifications apportées à l'original ne sont pas très importantes, mais elles permettent une étude plus réaliste du processus d'exploitation de la vulnérabilité. Les nouveaux fragments de code sont en gras.

 tinywebd.c #include <sys/stat.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <time.h> #include <signal.h> #include "hacking.h" #include "hacking-network.h" #define PORT 80 // ,     #define WEBROOT "./webroot" //   - #define LOGFILE "/var/log/tinywebd.log" //    int logfd, sockfd; //       void handle_connection(int, struct sockaddr_in *, int); int get_file_size(int); //         //  void timestamp(int); //        //       void handle_shutdown(int signal) { timestamp(logfd); write(logfd, " .\n", 16); close(logfd); close(sockfd); exit(0); } int main(void) { int new_sockfd, yes=1; struct sockaddr_in host_addr, client_addr; //    socklen_t sin_size; logfd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR); if(logfd == -1) fatal("  ");  if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)        fatal(" "); if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) fatal("   SO_REUSEADDR"); printf("  tiny web.\n"); if(daemon(1, 0) == -1) //    fatal("   "); signal(SIGTERM, handle_shutdown);   //    handle_shutdown signal(SIGINT, handle_shutdown);   //    handle_shutdown timestamp(logfd); write(logfd, ".\n", 15); host_addr.sin_family = AF_INET;       //    host_addr.sin_port = htons(PORT);     //  ,    host_addr.sin_addr.s_addr = INADDR_ANY; //    IP memset(&(host_addr.sin_zero), '\0', 8); //    if (bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr)) == -1) fatal("  "); if (listen(sockfd, 20) == -1) fatal("   "); while(1) { //   accept sin_size = sizeof(struct sockaddr_in); new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size); if(new_sockfd == -1) fatal(" "); handle_connection(new_sockfd, &client_addr, logfd); } return 0; } /*         *         FD. *    -,    *   .     . */ void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr, int logfd) { unsigned char *ptr, request[500], resource[500], log_buffer[500]; int fd, length; length = recv_line(sockfd, request); sprintf(log_buffer, " %s:%d \"%s\"\t", inet_ntoa(client_addr_ptr->sin_addr), ntohs(client_addr_ptr->sin_port), request); ptr = strstr(request, " HTTP/"); //    if(ptr == NULL) { //    HTTP  strcat(log_buffer, "  HTTP!\n"); } else { *ptr = 0; //      URL ptr = NULL; //  ptr  NULL (           //  ) if(strncmp(request, "GET ", 4) == 0) //  GET ptr = request+4; // ptr -  URL if(strncmp(request, "HEAD ", 5) == 0) //  HEAD ptr = request+5; // ptr -  URL if(ptr == NULL) { //     strcat(log_buffer, "  !\n"); } else { //    ptr,     if (ptr[strlen(ptr) - 1] == '/')   //  ,                                        //  '/', strcat(ptr, "index.html");   //    'index.html' strcpy(resource, WEBROOT);   //  resource                                            //  strcat(resource, ptr);   //      fd = open(resource, O_RDONLY, 0);  //    if(fd == -1) { //     strcat(log_buffer, " 404 Not Found\n");           send_string(sockfd, "HTTP/1.0 404 NOT FOUND\r\n");                send_string(sockfd, "Server: Tiny webserver\r\n\r\n");                send_string(sockfd, "<html><head><title>404 Not Found</title>                                     </head>");            send_string(sockfd, "<body><h1>URL not found</h1></body></html>                                     \r\n"); } else {       //      strcat(log_buffer, " 200 OK\n");             send_string(sockfd, "HTTP/1.0 200 OK\r\n");           send_string(sockfd, "Server: Tiny webserver\r\n\r\n");            if(ptr == request + 4) { //    GET     if( (length = get_file_size(fd)) == -1)          fatal("     ");     if( (ptr = (unsigned char *) malloc(length)) == NULL)          fatal("      ");          read(fd, ptr, length);  //              send(sockfd, ptr, length, 0);  //              free(ptr);  //      } close(fd); //   } //   if  /        } //   if     } //   if    HTTP timestamp(logfd); length = strlen(log_buffer); write(logfd, log_buffer, length); //    shutdown(sockfd, SHUT_RDWR); //    } /*         *    .    -1. */ int get_file_size(int fd) { struct stat stat_struct; if(fstat(fd, &stat_struct) == -1) return -1; return (int) stat_struct.st_size; } /*          *   . */ void timestamp(fd) { time_t now; struct tm *time_struct; int length; char time_buffer[40]; time(&now); //       time_struct = localtime((const time_t *)&now); //    tm length = strftime(time_buffer, 40, "%m/%d/%Y %H:%M:%S> ", time_struct); write(fd, time_buffer, length); //       } 

Ce programme crée un processus en double qui s'exécute en arrière-plan, écrit dans le fichier journal avec les horodatages et se termine correctement après avoir reçu le signal correspondant. Le descripteur de fichier journal et le socket acceptant la connexion sont déclarés en tant que variables globales afin que la fonction handle_shutdown () puisse terminer leur travail correctement. Il est défini comme un gestionnaire de rappel pour les signaux d'achèvement et d'interruption, ce qui garantit que le programme est terminé par la commande kill.

Voici le résultat de la compilation, de l'exécution et de l'achèvement du programme. Faites attention aux horodatages dans le journal et au message de fin qui est apparu après que le programme a reçu le signal correspondant et a appelé la fonction handle_shutdown ().

 reader@hacking:~/booksrc $ gcc -o tinywebd tinywebd.c reader@hacking:~/booksrc $ sudo chown root ./tinywebd reader@hacking:~/booksrc $ sudo chmod u+s ./tinywebd reader@hacking:~/booksrc $ ./tinywebd   tiny web. reader@hacking:~/booksrc $ ./webserver_id 127.0.0.1 The web server for 127.0.0.1 is Tiny webserver reader@hacking:~/booksrc $ ps ax | grep tinywebd 25058 ? Ss 0:00 ./tinywebd 25075 pts/3 R+ 0:00 grep tinywebd reader@hacking:~/booksrc $ kill 25058 reader@hacking:~/booksrc $ ps ax | grep tinywebd 25121 pts/3 R+ 0:00 grep tinywebd reader@hacking:~/booksrc $ cat /var/log/tinywebd.log cat: /var/log/tinywebd.log: Permission denied reader@hacking:~/booksrc $ sudo cat /var/log/tinywebd.log 07/22/2007 17:55:45> . 07/22/2007 17:57:00>  127.0.0.1:38127 "HEAD / HTTP/1.0" 200 OK 07/22/2007 17:57:21>  . reader@hacking:~/booksrc $ 

Le nouveau programme tinywebd traite le contenu HTTP, comme le tinyweb d'origine, mais se comporte comme un démon système car il n'a pas de terminal de contrôle et la sortie est vers un fichier journal. Les deux programmes sont vulnérables au même débordement de tampon - mais ce n'est que le début de cette vulnérabilité. Maintenant que nous avons choisi tinywebd comme cible de l'attaque, je vais vous montrer comment éviter la détection après être entré sur une autre machine.

»Plus d'informations sur le livre sont disponibles sur le site Web de l'éditeur
» Contenu
» Extrait

20% de réduction sur le coupon pour Habrozhitel - Hacking

Source: https://habr.com/ru/post/fr413989/


All Articles