• STATISTIQUES
  • Il y a eu un total de 1 membres et 4603 visiteurs sur le site dans les dernières 24h pour un total de 4 604 personnes!


    Membres: 2 604
    Discussions: 3 579
    Messages: 32 816
    Tutoriels: 78
    Téléchargements: 38
    Sites dans l'annuaire: 58


  • ANNUAIRE
  • [FR] Forum-Webmaster
    Une communauté webmaster pour apporter / recevoir de l'aide en création de site internet. Webmaster...
    Webmaster
    [FR] InfoMirmo
    Apprentissage de l'informatique par l'intermédiaire de challenges de sécurité. Venez app...
    Hacking
    [EN] Lost-chall
    Site de challenge présenté sous la forme de différente saison. Pour passer une saison vous devez avoir accumulÃ...
    Challenges
    [EN] osix
    Site de challenge qui utilise un système de level on chaque épreuve doit être réussie avant d'accédÃ...
    Challenges
    [EN] Net Force
    Javascript: 9, Java Applets: 6, Cryptography: 16, Exploits: 7, Cracking: 14, Programming: 13, Internet: 15, Steganograph...
    Challenges
    [FR] NewbieContest
    Nous vous proposons une série de challenges regroupant plusieurs domaines allant de l'exploitation de fail...
    Hacking
    [FR] Microcontest
    Cryptographie: 7, Mathématiques: 8, Image Son Vidéo: 5, Intelligence artificielle: 3, Réseau: 2, Divers: 7, Phy...
    Challenges

  • DONATION
  • Si vous avez trouvé ce site internet utile, nous vous invitons à nous faire un don du montant de votre choix via Paypal. Ce don servira à financer notre hébergement.

    MERCI!

    €



Note de ce sujet :
  • Moyenne : 0 (0 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
[Tutoriel] Cacher un service derrière un autre avec LD_PRELOAD
05-04-2016, 12h08 (Modification du message : 05-04-2016, 15h02 par ark.)
Message : #1
b0fh Hors ligne
Membre actif
*



Messages : 210
Sujets : 17
Points: 309
Inscription : Jul 2012
[Tutoriel] Cacher un service derrière un autre avec LD_PRELOAD
Bonjour,

Découvrons ensemble un petit gadget très pratique qui vous permettra, sous Linux, de dissimuler facilement (qui à dit backdoor ?) un service réseau derrière un autre, sans avoir à modifier du code existant, et sans faire de saletés avec iptables.

Spécifications

Le scénario typique est le suivant: vous êtes dans une entreprise/école/assoce/cybercafé fasciste qui interdit le traffic sortant vers tous les ports sauf le 80. Vous avez un serveur à disposition, mais vous ne voulez pas dédier le port 80 à SSH, car vous voulez garder votre serveur web existant. Il serait bien pratique d'avoir un gadget pour multiplexer les deux services sur le même port.

SSH comme HTTP ont un point en commun: le client envoie un message dès que la connexion est ouverte. Dans le cas de ssh2, c'est un banner qui commence par "SSH-2.0-<version du client>", alors
que dans le cas de HTTP c'est une requête comme "GET / HTTP/1.1". La taille minimum d'une requête HTTP, comme illustré, est donc de 16 bytes en HTTP 1.0, 22 bytes en HTTP 1.1. Il faudra décider en moins que ça quel protocole utiliser, pour éviter les I/O asynchrones, et le préfixe "SSH-2.0-" sera donc suffisant.

Dans les autres cas, nous souhaitons que par défaut, notre port se comporte comme un serveur HTTP (et donc renvoie l'erreur pertinente), et ne se comporte comme un serveur SSH que lorsqu'il est face à un client SSH.

Un serveur SSH doit tourner en root, pour pouvoir ensuite prendre l'identité de l'utilisateur qui se connecte. Mais faire tourner un serveur HTTP en root sera généralement une mauvaise idée. Pour contourner ce problème, en cas de connexion SSH, nous allons simplement rediriger le traffic vers le port SSH local, ce qui ne demande pas de privilèges particuliers, et laisser le sshd système s'en occuper. Pour ce faire, nous allons simplement exécuter netcat.

Architecture

Pour intercepter les connexions entrantes d'un processus, sans discrimination de protocole, nous allons intercepter l'appel systeme accept(). Pour mémoire, après avoir créé un socket, attaché a une addresse et un port locaux avec bind(), puis l'avoir placé en mode écoute avec listen(), un serveur doit appeler accept() en boucle. Cet appel est bloquant, jusqu'a ce qu'une nouvelle connexion arrive; accept() retourne alors un descripteur vers le socket spécifique a la connexion.

Sur un Linux moderne, Le symbole accept() est fourni par la libc, qui est liée dynamiquement. Nous allons utiliser le mécanisme LD_PRELOAD, décrit par mon estimé collègue ark dans un autre tuto, pour remplacer accept() par une autre version qui inclura notre fonctionnalité magique.

Le travail de notre remplacement à accept() sera donc d'appeler l'accept() original, puis de décider si la connexion doit être gérée de manière normale (auquel cas on passera la main au programme hôte comme si rien ne s'était passé), ou de manière alternative (auquel cas on exécutera un handler alternatif, sans informer l'hôte de l'existence de cette connexion). Le handler utilisera une interface similaire à celle utilisée par inetd, c'est-à-dire que le socket lui sera présenté sur stdin/stdout. Cette interface est utilisable avec le mode -i de sshd (si le programme hôte tourne en root), ou simplement avec netcat pour rediriger la connexion vers un autre port. C'est ce qui est fait ci-dessous.

Implémentation

Je vous laisse maintenant découvrir le code commenté pour l'occasion:

Code C :

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

/* Nécessaire pour RTLD_NEXT */
#define __USE_GNU
#include <dlfcn.h>

/* Le préfixe, envoyé par le client, qui doit déclencher l'alternative */
static const char trigger[] = "SSH-2.0-";
/* La commande alternative à lancer, dans un format acceptable pour execve() */
static char *(program[]) = { "nc", "-q0", "localhost", "22", NULL };

/* Notre wrapper pour accept(), avec le même prototoype que la fonction originale de la lib */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {

    /* Un pointeur vers la vraie fonction de la lib. On le déclare static pour ne pas avoir a
       le récupérer de manière répétée */

    static int (*_accept)(int, struct sockaddr *, socklen_t *) = NULL;
    char buf[sizeof(trigger)];

    /* RTLD_NEXT permet d'obtenir le prochain symbole de ce nom dans l'ordre de résolution standard,
       ce qui permet de le wrapper correctement */

    if (!_accept)
        _accept = dlsym(RTLD_NEXT, "accept");  

    /* Dans le cas ou la connexion est interceptée par la backdoor, il faudra boucler pour attendre
       la prochaine connexion */

    for( ;; ) {
       
        /* On appelle la version originale d'accept */
        int r = _accept(sockfd, addr, addrlen);

        /* Léger abus du court-circuitage */
        /* Si r est négatif (l'accept a échoué), on retourne l'erreur telle quelle */
        if (r <= 0 ||
            /* Sinon, on lit de quoi remplir buf (qui a la même taille que trigger.
               MSG_PEEK, très important, permet de laisser les données lues dans le buffer du kernel. Ainsi,
               si les données ne correspondent pas au trigger attendu, elles seront toujours disponibles
               pour l'application. Si la lecture échoue (trop peu de données), on retourne */

            recv(r, buf, sizeof(buf), MSG_PEEK) != sizeof(buf) ||
            /* On teste que les données lues correspondent au préfixe attendu */
            strncmp(buf, trigger, sizeof(buf) - 1)) {
                /* Dans chacun de ces cas, la connexion n'était pas intéressante pour nous, on retourne
                   donc le résultat du vrai accept à l'application comme si de rien n'était. */

                return r;
        }        

        /* A ce stade, nous avons décidé d'intercepter la connexion: on forke un handler. Le contenu du if
           sera seulement exécuté par l'enfant. Pour s'assurer de ne pas créer de zombies, et parce que
           notre parent ne s'attend pas à avoir des enfants surprise, on utilise le double-fork
           pour attacher notre handler à init */

        pid_t child = fork();
        if (!child) {
            /* On devient leader de session, ce qui assure que nous ne sommes plus attachés au terminal
               de contrôle du programme hooké, et donc on ne risque pas de recevoir de signaux intempestifs */

            setsid();
           
            /* Le child intermédiaire exit() immédiatement, ce qui débloque le parent.
               Le child final continuera réattaché à init, sans terminal de contrôle, et sans être
               leader de session */

            if (fork())
                exit(0);

            /* On émule une interface àla inetd, en réassignant la socket à stdout et stdin */
            dup2(r, 0);
            dup2(r, 1);

            /* Et on exécute le programme final */
            execvp(program[0], program);
        }
       
        /* On ferme la socket de la connexion, puisqu'elle est maintenant sous le contrôle du handler */
        close( r );

        /* On attend la fin du child intermédiaire, pour ne pas laisser de zombie */
        waitpid(child, NULL, 0);

        /* Et on retourne silencieusement au début de la boucle, qui relancera accept() pour se préparer
           Ã  acueillir la connexion suivante. Pour le programme hôte, la connexion qui vient d'avoir lieu
           n'a jamais existé. */


    }

}

 


Utilisation

On compile en objet partagé. L'option PIC est nécessaire comme l'addresse de chargement d'une lib peut changer. On linke avec la libdl, nécessaire pour intéragir avec le linker dyamique:

Code :
$> gcc -Wall -shared -fPIC -o magic.so magic.c -ldl

Lançons maintenant un serveur arbitraire, muni de notre greffon:

Code :
$> LD_PRELOAD=./magic.so python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000...

Ce serveur fonctionne comme un serveur http normal:

Code :
$> curl http://localhost:8000 >/dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   360  100   360    0     0  76416      0 --:--:-- --:--:-- --:--:-- 90000

Et la requête s'affiche dans le log serveur:

Code :
127.0.0.1 - - [05/Apr/2016 12:03:43] "GET / HTTP/1.1" 200 -

En revanche, si j'essaie avec SSH:

Code :
$> ssh -p 8000 localhost
The authenticity of host '[localhost]:8000 ([127.0.0.1]:8000)' can't be established.
ECDSA key fingerprint is 12:34:56:...
Are you sure you want to continue connecting (yes/no)?

Et rien dans le log du serveur :)
+1 (10) -1 (0) Répondre
05-04-2016, 14h56
Message : #2
fr0g Hors ligne
NTEuNDI2MzcsLTEuNzc4NDg4
*****



Messages : 348
Sujets : 22
Points: 56
Inscription : Aug 2011
RE: [Tutoriel] Cacher un service derrière un autre avec LD_PRELOAD
Pas bête, j'ai pas mal joué avec ld_preload et j'avais jamais pensé à ça Smile

merci pour le trick Smile
+1 (0) -1 (0) Répondre
06-04-2016, 09h52
Message : #3
thxer Hors ligne
:(){ :|:&amp; };:
*



Messages : 382
Sujets : 60
Points: 162
Inscription : Feb 2013
RE: [Tutoriel] Cacher un service derrière un autre avec LD_PRELOAD
Vraiment top ! Merci pour le partage et les explications !
Thxer.com
Twitter Thxer_

Code BASH :
echo "JkZ Palx" | sed 'y/lPZaJxk/MG3@tEH/'




+1 (0) -1 (0) Répondre
30-11-2016, 00h06
Message : #4
ZeR0-@bSoLu Hors ligne
Angel Of Byte
*



Messages : 43
Sujets : 2
Points: 4
Inscription : Feb 2016
RE: [Tutoriel] Cacher un service derrière un autre avec LD_PRELOAD
J'aime beaucoup , le petit tricks qui permet de belles choses Wink bien joué Wink
+1 (0) -1 (0) Répondre


Sujets apparemment similaires…
Sujet Auteur Réponses Affichages Dernier message
  Introduction a LD_PRELOAD ark 10 283 28-06-2012, 13h42
Dernier message: ark

Atteindre :


Utilisateur(s) parcourant ce sujet : 1 visiteur(s)
N-PN
Accueil | Challenges | Tutoriels | Téléchargements | Forum | Retourner en haut