
Este repositorio proporciona un entorno de aprendizaje para entender cómo funciona un exploit de RCE en Exim para CVE-2018-6789.
Este es un conjunto de archivos, scripts, notas, ... para configurar un entorno en el que investigar el RCE de Exim (CVE-2018-6789). Puede utilizarse para depurar Exim, escribir exploits, rastrear llamadas a funciones de Exim, aprender sobre la gestión de memoria personalizada de Exim (storeblocks), descubrir cómo funciona un exploit del mundo real, ...
¡Solo debe utilizarse con fines académicos!
vagrant-libvirt)Descarga el código fuente de Exim ejecutando
$ git submodule update --init
Hay un Vagrantfile en el directorio raíz. Utiliza libvirt como proveedor de virtualización. La box se descarga directamente del mirror de Fedora mediante box_url porque las descargas de Vagrant Cloud están actualmente rotas (migración a HCP); Fedora solo publica boxes de libvirt y VirtualBox allí (no de VMware), y un box_url directo es específico del proveedor, por lo que esta configuración está dirigida únicamente a libvirt.
# -*- mode: ruby -*-
# vi: set ft=ruby :
memory = 8192 # in MiB
cpus = 4
Vagrant.configure("2") do |config|
# Vagrant Cloud downloads are broken; pull the box from Fedora's mirror
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
Puedes cambiar la configuración como quieras, pero ten en cuenta que, por ejemplo, el script setup_vm.sh utiliza dnf para instalar paquetes. Si quieres usar Ubuntu debes reemplazar las líneas dnf install por apt-get install y ajustar los nombres de los paquetes en consecuencia. Sin embargo, no hay garantía de que la configuración funcione correctamente.
Cuando estés satisfecho con tu configuración, simplemente ejecuta:
$ vagrant up
para preparar la máquina y, después:
$ vagrant ssh
para conectarte a ella. Si no sabes cómo usar Vagrant, échale un vistazo aquí: https://www.vagrantup.com/intro/getting-started/
Vagrant mapea el directorio actual (es decir, el repositorio que acabas de clonar) como un directorio compartido en /vagrant. Para crear y ejecutar la imagen Docker de Exim introduce los siguientes comandos dentro de tu VM (vagrant ssh)
[vagrant@localhost ~]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
La primera vez tardará mucho más porque Exim se compilará desde el código fuente. Si modificas scripts de depuración u otros archivos que se copiarán dentro del contenedor Docker, siempre puedes usar ./scripts/reset_docker.sh para reconstruir la imagen Docker. Por supuesto, también puedes extraer las líneas necesarias del script y ejecutarlas como comandos individuales.
Cuando todo esté listo deberías ver una consola de root:
Successfully tagged exim:latest
787f310ef922a1e519cf8bb47f1c4fed5f510da705e7ceefd48f160c980e969c
root@787f310ef922:/opt#
Las cadenas extrañas pueden verse diferentes en tu máquina, pero ahora estás dentro de un contenedor Docker de Debian ejecutándose en una VM de Fedora sobre tu máquina anfitriona.
Primero, puedes crear dos sesiones SSH con vagrant ssh en dos ventanas de terminal. Una se puede usar para ejecutar exploits e interactuar con Exim a través de SMTP. La otra se usa para iniciar, ejecutar, depurar, ... Exim dentro del contenedor Docker. ASLR está deshabilitado en la VM, por lo que puedes establecer breakpoints fiables que no cambien durante las sesiones de depuración.
Ejemplo de sesión:
Primer terminal:
$ vagrant ssh
[vagrant@localhost vagrant]$
Segundo terminal:
$ vagrant ssh
[vagrant@localhost vagrant]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
...
# now you are inside the Debian Docker container
root@99296cf63016:/opt# ./run_exim.sh
root@99296cf63016:/opt# ./attach_exim.sh
El script run_exim.sh termina y Exim se ejecuta en segundo plano. El script ./attach_exim.sh debería adjuntar gdb al proceso demonio de Exim en ejecución y mostrarte una salida como esta:
...
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 ejecución y esperando peticiones. Los breakpoints que se han establecido provienen del archivo debugging/breakpoints. Puedes usar Ctrl+C para interrumpir el proceso y dar el control a gdb. También podrías ejecutar uno de los scripts de exploit proporcionados para comprobar si todo funciona como se espera:
Primer terminal:
[vagrant@localhost ~]$ cd /vagrant/sploits/
[vagrant@localhost sploits]$ ./sploit_0.py
[+] Opening connection to localhost on port 25: Done
Segundo 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>
Puedes eliminar todos los breakpoints con d y continuar con c para dejar que el script sploit_0.py se ejecute hasta que termine:
Segundo terminal:
Breakpoint smtp_reset
pwndbg> d
pwndbg> c
Continuing.
[Inferior 2 (process 42) exited with code 01]
Primer 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 el proceso hijo que acabas de depurar (al que te adjuntaste) termina (p. ej. [Inferior 2 (process 42) exited with code 01]), puedes salir de gdb y ejecutar ./attach_exim.sh de nuevo.
En debugging puedes encontrar algunos scripts que podrían ser útiles. Uno de ellos es showmem.py. Te permite inspeccionar los storeblocks de Exim y los chunks de heap correspondientes. Puedes ejecutarlo dentro de gdb con el comando 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:
Las regiones de memoria indentadas son los storeblocks; las demás regiones son chunks de heap (glibc). Actualmente esto es una aproximación, ya que no he contrastado los chunks en uso con las listas libres de glibc, por lo que puede haber algunas indicaciones incorrectas de bloques usados/libres. ¡Siempre puedes usar los comandos bins, heap, ... de pwndbg como fuente adicional de información!
Nota: con libvirt / KVM la carpeta /vagrant se sincroniza mediante rsync, así que después de editar archivos en el host ejecuta vagrant rsync (o mantén vagrant rsync-auto en ejecución) para copiar tus cambios en la VM antes de reconstruir el contenedor.
.
├── debugging # GDB related scripts
│ ├── breakpoints
│ ├── gdbinit
│ ├── showmem.py
│ └── trace.py
├── Dockerfile # Dockerfile to build and debug Exim
├── Exim # Source code for the vulnerable Exim version
├── exim_code_backup # backup of the Exim's vulnerable source code
│ └── exim-fc6d65867e82009a6e0671771728d41d3423a790.zip
├── exim_files # Patched Exim files to build Exim correctly
│ ├── configure
│ ├── eximon.conf
│ ├── Makefile
│ └── Makefile-Linux
├── README.md
├── scripts # Helper scripts to debug Exim
│ ├── attach_exim.sh
│ ├── reset_docker.sh
│ ├── run_exim.sh
│ └── setup_vm.sh
├── sploits # Incremental exploit scripts and a script to find the 0xf1 byte
│ ├── 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 to create the VM that hosts the Docker container
Los scripts de exploit se encuentran en sploits. Están construidos de forma incremental para que sea más fácil entender los distintos pasos. Son casi idénticos a los pasos proporcionados por @straightblast426 en medium.com.
sploit_10.py es el script final que debería demostrar el RCE en un solo script. Este script no lanza una reverse shell, sino que crea un archivo en /tmp. Puedes modificarlo editando la siguiente línea:
cmd = '/bin/bash -c "touch /tmp/pwned"'
Ten en cuenta que el script sploit_10.py proporcionado no es la única forma de explotar la vulnerabilidad. ¡Hay, por ejemplo, otras formas de organizar el heap!
El grooming es sensible al entorno: mantén host_lookup = deshabilitado (un nombre de cliente de DNS inverso contamina el heap que el exploit prepara) y el contenedor en debian:bullseye
(el grooming solo sobrevive con la glibc 2.31 de Debian, no con la de Ubuntu ni con la de bookworm). Esto también significa
que este repositorio solo funcionará (tal cual) mientras exista una distribución que proporcione glibc 2.31 (publicada por primera vez en 2020)!
El puntero next se modificará con una dirección conocida de antemano (que también debería funcionarte si usas la configuración idéntica). Si lanzaras este exploit contra otra instancia de Exim en ejecución, no funcionaría (las probabilidades son muy pequeñas). Podrías evadir ASLR con algo de fuerza bruta. Esto funciona bastante bien porque Exim hace fork (clone) de sí mismo para gestionar las peticiones de los clientes. Eso significa que la disposición general de la memoria permanece igual y tienes una oportunidad realista de forzar por fuerza bruta el puntero next. Si no sabes qué es el puntero next, deberías leer primero las referencias.
Si compilas exim de forma ligeramente diferente, la ubicación del puntero next puede ser diferente. Tienes que encontrar la dirección del storeblock que contiene acl_smtp_rcpt. Sigue estos pasos y ajusta el puntero en los archivos sploit_xx.py correspondientes:
# connect to VM with: vagrant ssh
./run_exim.sh
./attach_exim.sh
# pwndbg starts, then Ctrl-C
pwndbg> smem
[SHOWMEM]: 0x55555562e6b0: heap chunk of size 0x00002020 (used) / data: .2cUUU
[SHOWMEM]: 0x55555562e6c0: storeblock of size 0x00002000 / data: /usr/exim/configure
...
El storeblock con dirección 0x55555562e6c0 también contiene acl_smtp_rcpt:
pwndbg> p acl_smtp_rcpt
$1 = (uschar *) 0x55555562e7f0 "acl_check_rcpt"
Así que en los scripts de exploit, reemplázala con la dirección que muestra el comando smem:
# ...
# address of the ACL strings storeblock (not the chunk)
address_of_acl_storeblock = 0x55555562E6C0
# ...