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-2021-4034-CTF-writeup — Writeup de desafío CTF pwn que explota CVE-2021-4034 (pkexec) mediante manipulación del heap, con ingeniería inversa basada en Ghidra y un binario auxiliar personalizado shelly.so. | Kitploit
Herramientas/GitHubGitHub/wechicken456/cve-2021-4034-ctf-writeup
ExplotaciónIngeniería InversaCTFAprendizaje y EducaciónExplotación de BinariosLabs y Práctica
GitHubwechicken456/cve-2021-4034-ctf-writeup

CVE-2021-4034-CTF-writeup

Writeup de desafío CTF pwn que explota CVE-2021-4034 (pkexec) mediante manipulación del heap, con ingeniería inversa basada en Ghidra y un binario auxiliar personalizado shelly.so.

Ver Repositorio
25hace 2 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-2021-4034-CTF-writeup

Este es un reto CTF de tipo pwn que escribí en C y que requiere que el usuario explote la vulnerabilidad CVE-2021-4034. Los jugadores reciben 2 binarios en el directorio challenge de este repositorio. El binario chal implementa el reto CTF y shelly.so es un binario auxiliar.

Cómo emular este reto

Al momento de escribir este writeup, el Dockerfile aún no está completo. El Dockerfile es necesario para desplegar este reto durante un CTF en vivo, pero no localmente: aún puedes emular este reto localmente configurando los permisos de usuario e instalando los paquetes vulnerables de la siguiente manera:

  1. Para explotar esta vulnerabilidad con éxito, los jugadores que no estén en una máquina Linux deberían instalar primero una VM Linux. Después, necesitarás instalar un kernel vulnerable. Instrucciones sobre cómo hacerlo: [https://askubuntu.com/a/700221]
  2. Instala los paquetes libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1.
  3. Crea un archivo flag.txt propiedad de en el directorio actual.
root:root
  • Crea un usuario sin privilegios. Cambia a este usuario.
  • Descarga los archivos de la carpeta challenge al directorio actual.
  • Ejecuta chal como el usuario sin privilegios.
  • Análisis a ciegas

    Ejecutar el binario chal nos da una idea vaga de lo que hace este binario:``` WELCOME TO THE HUB CTRL+ALT+DELICIOUS We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every byte

    1. ENTER THE HUB
    2. QUIT 1 Order number: 0x7ffde93681f0 Enter your name: tin
    3. ADD NEW ORDER
    4. EDIT ORDER
    5. SHOW ORDER
    6. CANCEL ORDER
    7. CHECKOUT
    8. DONE 1 Pick your bread: aaaa Select your spread: bbbb Choose your veg: cccc Slam your meat & egg: dddc Any side notes for the cook? 0000
    9. ADD NEW ORDER
    10. EDIT ORDER
    11. SHOW ORDER
    12. CANCEL ORDER
    13. CHECKOUT
    14. DONE 1 Pick your bread: AAAA Select your spread: BBBB Choose your veg: CCCC Slam your meat & egg: DDDD Any side notes for the cook? 1111
    15. ADD NEW ORDER
    16. EDIT ORDER
    17. SHOW ORDER
    18. CANCEL ORDER
    19. CHECKOUT
    20. DONE 3 Enter order index: 0 aaaa, bbbb, cccc, dddc, 0000
    21. ADD NEW ORDER
    22. EDIT ORDER
    23. SHOW ORDER
    24. CANCEL ORDER
    25. CHECKOUT
    26. DONE 3 Enter order index: 1 AAAA, BBBB, CCCC, DDDD, 1111
    27. ADD NEW ORDER
    28. EDIT ORDER
    29. SHOW ORDER
    30. CANCEL ORDER
    31. CHECKOUT
    32. DONE 4 Enter order index: 1
    33. ADD NEW ORDER
    34. EDIT ORDER
    35. SHOW ORDER
    36. CANCEL ORDER
    37. CHECKOUT
    38. DONE 3 Enter order index: 1 Invalid index!
    39. ADD NEW ORDER
    40. EDIT ORDER
    41. SHOW ORDER
    42. CANCEL ORDER
    43. CHECKOUT
    44. DONE 5 /notes ./tin/notes
    45. ADD NEW ORDER
    46. EDIT ORDER
    47. SHOW ORDER
    48. CANCEL ORDER
    49. CHECKOUT
    50. DONE 6
    51. ENTER THE HUB
    52. QUIT 2 Come again :)
    root@kitploit:~
    Jugar con el tamaño de entrada en la función `Add` y con los índices de las funciones `Cancel` no nos da nada especial (sin desbordamiento ni fallo de segmentación). Sin embargo, algunas cosas interesantes:
      - Parece que los pedidos se colocan en una lista (quizás una lista enlazada) y ¿están indexados desde 0?
      - Hay este `Order number: 0x7ffde93681f0` que parece imprimir alguna ubicación en la pila?
      - Además, el programa crea un directorio con nuestro nombre de entrada junto con 3 ejecutables, uno de los cuales es el archivo binario auxiliar que se nos proporciona:```sh
    peasant@Tin-VM:~/Desktop$ ls
    chal  chal.c  Dockerfile shelly.so  solve.py  tin
    peasant@Tin-VM:~/Desktop$ ls -l tin/
    total 28
    -rwxrwx--- 1 peasant vboxsf     5 Feb  4 14:33 notes
    -rwxrwx--- 1 peasant vboxsf    20 Feb  4 14:33 recipe
    -rwxr-x--- 1 peasant vboxsf 16488 Feb  4 14:33 shelly.so
    peasant@Tin-VM:~/Desktop$ cat tin/notes 
    0000
    peasant@Tin-VM:~/Desktop$ cat tin/recipe 
    aaaa
    bbbb
    cccc
    dddc
    
    

    Los ejecutables contienen nuestra entrada.


    Análisis con Ghidra

    Puedes jugar con las demás funciones y esperar a toparte con algunos bugs (lo cual es probable), pero voy a ir al grano y abrir el programa en Ghidra.

    Comparando las cadenas que aparecen al ejecutar el programa con las cadenas presentes en Ghidra, podemos renombrar algunas de las funciones FUN_* con nombres familiares:```C undefined8 main(void)

    { int iVar1; size_t sVar2; undefined2 *puVar3; long in_FS_OFFSET; int opt; int local_1c; char *local_18; long local_10;

    local_10 = *(long *)(in_FS_OFFSET + 0x28); local_18 = "/recipe"; print("WELCOME TO THE HUB CTRL+ALT+DELICIOUS\n"); print( "We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every by te\n\n" ); while( true ) { print("1. ENTER THE HUB\n"); print("2. QUIT\n"); __isoc99_scanf(&DAT_001030c5,&opt); getc(stdin); if (opt != 1) break; printf("Order number: %p\n",&local_18); print("Enter your name: "); __isoc99_scanf(&DAT_0010334f,&DAT_00105120); sVar2 = strlen(&DAT_00105120); puVar3 = (undefined2 *)malloc(sVar2 + 2); DAT_00105100 = puVar3; *puVar3 = 0x2f2e; *(undefined *)(puVar3 + 1) = 0; strcpy((char *)(DAT_00105100 + 1),&DAT_00105120); iVar1 = FUN_001022f0(DAT_00105100,&DAT_00105060); if (iVar1 == -1) { mkdir((char *)DAT_00105100,0x1c0); } DAT_00105140 = 0; order_cnt = 0; for (local_1c = 0; local_1c < 10; local_1c = local_1c + 1) { *(undefined8 *)(&ptr_array + (long)local_1c * 8) = 0; } main_menu(); } print("Come again :)\n");

    root@kitploit:~
    The `printf("Order number: %p\n",&local_18);` **imprime la ubicación de una variable local en la pila.**
    
    Una verificación rápida en gdb nos muestra que la dirección filtrada es la dirección del puntero a la cadena constante `/recipe`:```gdb
    ...
    Order number: 0x7fffffffdfc0
    ...
    gef➤  x/gx 0x7fffffffdfc0
    0x7fffffffdfc0:	0x0000555555559020
    gef➤  x/s 0x0000555555559020
    0x555555559020:	"/recipe"
    

    Vemos una llamada a mkdir, que crea un directorio con nuestro nombre de entrada en el directorio actual.

    Estos son consistentes con nuestra observación cuando ejecutamos el programa. Luego inicializa alguna variable antes de llamar a la función main_menu, que se ve algo así:```C while( true ) { while( true ) { while( true ) { while( true ) { while( true ) { while( true ) { print("1. ADD NEW ORDER\n"); print("2. EDIT ORDER\n"); print("3. SHOW ORDER\n"); print("4. CANCEL ORDER\n"); print("5. CHECKOUT\n"); print("6. DONE\n"); __isoc99_scanf(&DAT_001030c5,&local_40); getc(stdin); if (local_40 != 1) break; add_order(); } if (local_40 != 2) break; edit_order(); } if (local_40 != 3) break; show_order(); } if (local_40 != 4) break; cancel_order(); } if (local_40 != 5) break; checkout(); } if (local_40 == 6) break; if (local_40 == 0x539) { print( "\nGORDON RAMSAY: Finally, a worthy opponent, our battle will be legendary! I BET YOU CAN 'T GUESS THE SECRET RECIPE.\n" ); fgets(inp,0x20,stdin); getrandom(random-bytes,0x10,0); for (local_3c = 0; local_3c < 0x10; local_3c = local_3c + 1) { if (inp[local_3c] != random-bytes[local_3c]) { print("...Nuh Uh!...\n"); /* WARNING: Subroutine does not return */ exit(0); } print("...Ooh Yes.. sCruMpTioUs..."); } print("Fine... I'll give you a taste.\n"); FUN_00101504(); }

    root@kitploit:~
    Si no estás familiarizado con REV, esta es la descompilación de la sentencia `switch` en C. Hay una opción interesante, también conocida como `0x539`. Permite a los jugadores adivinar `0x10` bytes aleatorios. Si todos los bytes son iguales, entonces llama a `FUN_00101504();`, que llama a `system("cat flag.txt");`. De lo contrario, el programa sale.
    
    Sin embargo, intentar adivinar por fuerza bruta 16 bytes aleatorios equivale a probar todas las 256**16 = 340282366920938463463374607431768211456 posibilidades. Buena suerte con esto lol.
    
    Incluso si pruebas todas las posibilidades, el programa sigue sin privilegios, así que no puede leer la flag. Esta sentencia `cat flag.txt` es intencional, no solo para engañar a los jugadores sin experiencia y hacer que elijan esta opción del menú `0x539`, sino también para evitar que los jugadores simplemente llamen a esta función para leer la flag, cuestión en la que profundizaremos más adelante.
    ***
    ## Agregar```C
      int iVar1;
      undefined8 *puVar2;
      void *pvVar3;
      undefined8 *ptr2;
      
      if (order_cnt < 10) {
        puVar2 = (undefined8 *)malloc(0x30);
        print("Pick your bread: ");
        readline(puVar2 + 1,8);
        print("Select your spread: ");
        readline(puVar2 + 2,8);
        print("Choose your veg: ");
        readline(puVar2 + 3,8);
        print("Slam your meat & egg: ");
        readline(puVar2 + 4,9);
        iVar1 = order_cnt;
        pvVar3 = malloc(0x30);
        *(void **)(&ptr_array + (long)iVar1 * 8) = pvVar3;
        *puVar2 = *(undefined8 *)(&ptr_array + (long)order_cnt * 8);
        print("Any side notes for the cook? ");
        readline(*puVar2,0x30);
        puVar2[5] = 0;
        if (DAT_00105140 != (undefined8 *)0x0) {
          for (ptr2 = DAT_00105140; ptr2[5] != 0; ptr2 = (undefined8 *)ptr2[5]) {
          }
          ptr2[5] = puVar2;
          puVar2 = DAT_00105140;
        }
        DAT_00105140 = puVar2;
        order_cnt = order_cnt + 1;
      }
    ...
    

    Si algunas de las variables no te resultan fáciles de leer de esta forma, es porque me tomé un tiempo para renombrarlas así, algo que deberías hacer mientras haces ingeniería inversa para mantener el registro de las cosas. Bien, vamos a los puntos principales:

    • puVar2 es un chunk de malloc que tiene un tamaño de 0x30.
    • El campo 0 es el puntero puVar3, que tiene 8 bytes de largo y apunta a los notes del chunk actual.
    • Los siguientes 4 campos tienen cada uno 8 bytes de largo, aunque podemos leer 9 bytes en el cuarto campo, por lo que podemos desbordar 1 byte en el quinto campo.
    • Sin embargo, la línea puVar2[5] = 0; sobrescribe de todos modos nuestro desbordamiento de 1 byte con NULL.
    • Luego, si la variable global DAT_00105140 == 0, simplemente la establecemos al nuevo chunk, así que podría ser un puntero head de la lista.
    • De lo contrario, iteramos hasta el último chunk de la lista actual y establecemos su quinto campo al nuevo chunk. => El quinto campo es el puntero al siguiente chunk en la lista enlazada.

    Editar```C

    ... print("Enter order index: "); __isoc99_scanf(&DAT_001030c5,&local_20); getc(stdin); if ((local_20 < 0) || (order_cnt <= local_20)) { print("Invalid index!\n"); } else { local_18 = head; for (local_1c = 0; local_1c != local_20; local_1c = local_1c + 1) { local_18 = (undefined8 *)local_18[5]; } print("Pick your bread: "); readline(local_18 + 1,8); print("Select your spread: "); readline(local_18 + 2,8); print("Choose your veg: "); readline(local_18 + 3,8);WE print("Slam your meat & egg: "); readline(local_18 + 4,9); print("Any side notes for the cook? "); readline(*local_18,0x30); } ...

    root@kitploit:~
    Hay una comprobación para nuestro índice de entrada, así que no podemos editar ubicaciones arbitrarias.
    
    **Sin embargo, la lectura de 9 bytes en el 4.º campo, que se desborda 1 byte hacia el 5.º campo, sigue ahí!!! Podemos sobrescribir 1 byte en el puntero `nxt`**
    ***
    ## Show```C
    ...
        if (local_18 != (undefined8 *)0x0) {
          printf("%s, %s, %s, %s, %s\n",local_18 + 1,local_18 + 2,local_18 + 3,local_18 + 4,*local_18);
        }
    ...
    

    %s imprimirá hasta el carácter NULL, así que si nuestro 4.º campo tiene 8 bytes de longitud, entonces el 4.º %s imprimirá el 4.º campo de nuestro chunk + el valor del puntero nxt en el 5.º campo. => ¡Conseguimos una fuga de heap!!!*


    Cancel

    Nada interesante aquí.


    Checkout```C

    strcpy(local_d8,dir_name); sVar2 = strlen(dir_name); strcpy(local_d8 + sVar2,"/recipe"); creat(local_d8,0x1c0); iVar1 = open(local_d8,2); if (iVar1 == -1) { print("Error opening file f.\n"); /* WARNING: Subroutine does not return / exit(0); } chmod(local_d8,0x1f8); strcpy(local_98,dir_name); sVar2 = strlen(dir_name); strcpy(local_98 + sVar2,"/notes"); printf("%s %s\n","/notes",local_98); creat(local_98,0x1c0); __fd = open(local_98,2); if (__fd == -1) { print("Error opening file f_notes.\n"); / WARNING: Subroutine does not return */ exit(0); } chmod(local_98,0x1f8);

    root@kitploit:~
    Así que crea los archivos `recipe` y `notes` en el directorio con nuestro nombre de entrada. Las instrucciones `chmod` establecen estos archivos en modo ejecutable: `0x1f8` y `0x1c0` son `0700` y `0770` en base octal, respectivamente.```C
    ...
      for (local_ec = 0; (local_e0 != (char **)0x0 && (local_ec < order_cnt)); local_ec = local_ec + 1)
      {
        sVar2 = strlen((char *)(local_e0 + 1));
        write(iVar1,local_e0 + 1,sVar2);
        write(iVar1,&DAT_00103127,1);
        sVar2 = strlen((char *)(local_e0 + 2));
        write(iVar1,local_e0 + 2,sVar2);
        write(iVar1,&DAT_00103127,1);
        sVar2 = strlen((char *)(local_e0 + 3));
        write(iVar1,local_e0 + 3,sVar2);
        write(iVar1,&DAT_00103127,1);
        sVar2 = strlen((char *)(local_e0 + 1));
        write(iVar1,local_e0 + 4,sVar2);
        write(iVar1,&DAT_00103127,1);
        sVar2 = strlen(*local_e0);
        write(__fd,*local_e0,sVar2);
        write(__fd,&DAT_00103127,1);
        local_e0 = (char **)local_e0[5];
      }
      iVar1 = close(iVar1);
      if (-1 < iVar1) {
        iVar1 = close(__fd);
        if (-1 < iVar1) {
          local_58 = 0x2f706d742f207063;
          local_50 = 0x732e796c6c656873;
          local_48 = 0x206f;
          local_40 = 0;
          local_38 = 0;
          local_30 = 0;
          local_28 = 0;
          local_20 = 0;
          strcpy((char *)((long)&local_48 + 2),dir_name);
          system((char *)&local_58);
          if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                        /* WARNING: Subroutine does not return */
            __stack_chk_fail();
          }
          return;
        }
      }
    ...
    

    Este enorme y desordenado bloque de código básicamente escribe el contenido de nuestros fragmentos en estos archivos, y luego ejecuta el comando cp /tmp/shelly.so dir_name, donde dir_name es el nombre que le damos al programa al principio.


    FUN_00101e84```C

    void FUN_00101e84(void)

    { long in_FS_OFFSET; char *local_18; long local_10;

    local_10 = *(long *)(in_FS_OFFSET + 0x28); puts("HOLD UP, LET HIM COOK."); local_18 = (char *)0x0; execve("/usr/bin/pkexec",&local_18,(char **)&ptr_array); if (local_10 != *(long )(in_FS_OFFSET + 0x28)) { / WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }

    root@kitploit:~
    Esta es una función interesante. Llama a `pkexec` con un `NULL argv` y el puntero del array `notes` como variables de entorno, que es el comando vulnerable en nuestro CVE-2021-4034. ¿Esto sugiere que tenemos que manipular nuestros `notes` y redirigir la ejecución a esta función de alguna manera?
    
    ***
    ## shelly.so
    Abrirlo en Ghidra nos dirá que es obviamente una biblioteca `set-UID-root`, similar a las utilizadas en la explotación del CVE.
    
    ***
    # Resumen
    Algunos puntos importantes:
    1. Tenemos una fuga de pila al comienzo del programa (número de pedido) que contiene la dirección de la cadena constante `/recipe` utilizada para crear el archivo `recipe`.
    2. El campo 0 de un chunk es su puntero `notes`.
    3. El campo 5 de un chunk es el puntero `nxt` al siguiente chunk en la lista enlazada.
    4. `Show` puede filtrar una dirección del heap que es el campo 5 de un chunk.
    5. `Edit` puede sobrescribir 1 byte en el campo 5. **¡¡ESTE ES EL ÚNICO BUG DE ESCRITURA ARBITRARIA!!**
    6. `FUN_00101e84` llama a `pkexec` con argv NULL y las variables de entorno que controlamos (array `notes`).
    ***
    # Explotación
    
    ## Disposición de los chunks del heap
    Una inspección rápida en gdb puede decirnos el desplazamiento entre los distintos chunks de nuestro programa:```gdb
    ...
    Pick your bread: aaaa
    Select your spread: a
    Choose your veg: a
    Slam your meat & egg: a
    Any side notes for the cook? 0000
    1. ADD NEW ORDER
    2. EDIT ORDER
    3. SHOW ORDER
    4. CANCEL ORDER
    5. CHECKOUT
    6. DONE
    1
    Pick your bread: bbbb
    Select your spread: b
    Choose your veg: b
    Slam your meat & egg: b
    Any side notes for the cook? 1111
    ...
    gef➤  search-pattern aaaa
    [+] Searching 'aaaa' in memory
    [+] In '[heap]'(0x55555555a000-0x55555557b000), permission=rw-
      0x55555555aae8 - 0x55555555aaec  →   "aaaa" 
    gef➤  x/30gx 0x55555555aae8-0x18
    0x55555555aad0:	0x0000000000000000	0x0000000000000041
    0x55555555aae0:	0x000055555555ab20	0x0000000061616161
    0x55555555aaf0:	0x0000000000000061	0x0000000000000061
    0x55555555ab00:	0x0000000000000061	0x000055555555ab60
    0x55555555ab10:	0x0000000000000000	0x0000000000000041
    0x55555555ab20:	0x0000000030303030	0x0000000000000000
    0x55555555ab30:	0x0000000000000000	0x0000000000000000
    0x55555555ab40:	0x0000000000000000	0x0000000000000000
    0x55555555ab50:	0x0000000000000000	0x0000000000000041
    0x55555555ab60:	0x000055555555aba0	0x0000000062626262
    0x55555555ab70:	0x0000000000000062	0x0000000000000062
    0x55555555ab80:	0x0000000000000062	0x0000000000000000
    0x55555555ab90:	0x0000000000000000	0x0000000000000041
    0x55555555aba0:	0x0000000031313131	0x0000000000000000
    0x55555555abb0:	0x0000000000000000	0x0000000000000000
    

    Entonces los chunks son de tamaño 0x40 (incluyendo 0x10 bytes de metadatos), y están separados (desde el inicio del chunk actual hasta el inicio del siguiente chunk) por un offset de 0x40+0x40 = 0x80. Esto se debe a que los notes se asignan cada vez que asignamos un chunk, por lo que siempre quedan entre chunks consecutivos.

    Escribir en una ubicación arbitraria

    Antes de saltar al exploit, ¿cómo abusamos de los bugs de desbordamiento de 1 byte para escribir en una dirección arbitraria? Podemos usar la siguiente estrategia:

    1. Asignar 3 chunks A , B , C.
    2. Filtrar el puntero nxt del 2º chunk (2º punto en la sesión Summary).
      • La dirección filtrada (que es el siguiente chunk) es C.
      • Entonces, la dirección del chunk actual será B = C-0x80.
      • Sea x el byte MENOS significativo de B.
    3. Desbordar el byte x + 8 en el 5º campo del chunk actual. El puntero nxt resultante será B + 8.
      • Tenemos que tener cuidado aquí: usando el fragmento de memoria de arriba, si tomamos el primer chunk como el actual, es decir, A = 0x55555555aae0, entonces la dirección filtrada sería B = 0x55555555ab60 y x = 0xe8.
      • Si sobrescribimos el último byte en el 5º campo de A en 0x55555555ab08 con x, el puntero nxt resultante será 0x55555555**ab**e8 != 0x55555555**aa**e8, que no es lo que queríamos.
      • Por lo tanto, tenemos que elegir un chunk actual y el siguiente chunk de modo que su SEGUNDO byte MENOS significativo sean iguales.
    4. Ahora tenemos que el 3er chunk está en B + 8 en lugar de C. Edita el chunk B para que su 1er campo (bread) contenga la dirección target a la que queremos escribir.
    5. Edita el 3er chunk, lo que sobrescribirá el contenido a partir de B + 8. ¡El puntero notes de este 3er chunk es el target! Así que todo lo que escribamos en notes se escribirá en target.

    1ª forma - saltar a "cat flag.txt"

    Hay una forma de saltar a esta función, que no voy a comentar. Pero antes de intentarlo, echa un vistazo al Dockerfile. ¿Notas algo sobre flag.txt?``` ... chown root:root /home/ctf/flag.txt ... USER peasant CMD ["/home/ctf/start.sh"]

    root@kitploit:~
    ***ES PROPIEDAD DE ROOT, MIENTRAS QUE EL PROGRAMA SE EJECUTA Y ES PROPIEDAD DE UN USUARIO SIN PRIVILEGIOS***.
    Así que aunque ejecutes "cat flag.txt", no va a imprimir la flag porque el programa no tiene permiso para acceder al archivo. Esto implica que necesitamos escalar a root para poder leer la flag.
    ## 2.ª forma - CVE-2021-4034
    Para explotar CVE-2021-4034, necesitamos la siguiente configuración:
    1. Un directorio con el nombre `GCONV_PATH=.`
    2. En este directorio, necesitamos un archivo ejecutable que sea el nombre del directorio donde estará nuestro archivo de configuración `gconv-modules`. Que sea `recipe` en nuestro reto.
    3. En el directorio `recipe`, necesitamos un archivo llamado `gconv-modules` con el contenido que controlemos, y una biblioteca dinámica que nos lance una shell (¿quizás es el binario proporcionado `shelly.so`)?
    4. Después, en nuestro `gconv-modules`, necesitamos tener el mismo CHARSET que en el paso 5 de abajo, y el nombre de nuestra biblioteca dinámica `shelly.so` así: `module UTF-8// SHELLY// shelly 2`.
    5. Después de todo esto, llamamos a `pkexec` con argv NULL y el array de variables de entorno manipulado = `{"recipe", "PATH=GCONV_PATH=.", "CHARSET=SHELLY", "SHELL=shelly", NULL}`.
    
    Ahora, explotamos este reto para conseguir la configuración anterior.
    
    ***
    ### Objetivo 1-2
    Los pasos 1-2 son fáciles: solo tenemos que iniciar sesión con un nombre de usuario `GCONV_PATH=.` para crear este directorio. Luego hacemos un `checkout` para crear el ejecutable `recipe` en este directorio. Después volvemos al primer menú.
    ***
    ### Objetivo 3-4
    Iniciamos sesión con el nombre de usuario `recipe` para crear este directorio. Ahora, *necesitamos* crear un archivo llamado `gconv-modules`, pero `checkout` solo crea los archivos `recipe` y `notes`.
    
    ¿Y si sobrescribimos la ubicación de memoria para la cadena `/notes` y la convertimos en `/gconv-modules`? Esto podría funcionar, pero requiere que esa memoria sea escribible. Vamos a comprobarlo en gdb:```gdb
    gef➤  search-pattern /notes
    [+] Searching '/notes' in memory
    [+] In '/home/peasant/Desktop/chal'(0x555555559000-0x55555555a000), permission=rw-
      0x555555559010 - 0x555555559016  →   "/notes" 
    

    ¡Y es escribible!

    ¿Cómo realizamos la escritura?

    1. Usamos la estrategia de escritura arbitraria descrita anteriormente para LEER arbitrariamente la ubicación de la dirección de pila filtrada. Esto nos indica dónde está /recipe.
    2. Una inspección rápida en gdb puede indicarnos que el desplazamiento desde /recipe hasta /notes es 0x10. Resta esto de la dirección de /recipe en el paso 1 para obtener la dirección de /notes.
    3. Usa la estrategia de escritura arbitraria anterior para sobrescribir /notes con /gconv-modules.

    En cuanto a escribir el contenido correcto en el archivo gconv-modules, sabemos que en checkout, el contenido de notes de los chunks se escribirá en /notes, que ahora es /gconv-modules. Para asegurarnos, podemos simplemente proporcionar la cadena module UTF-8// SHELLY// shelly 2 a cada notes.

    Así que ahora, cada vez que llamemos a checkout, creará 2 archivos recipe y gconv-modules y escribirá algunas líneas module UTF-8// SHELLY// shelly 2 (usamos shelly porque ese es el nombre de la biblioteca .so set-uid-root proporcionada) en el archivo gconv-modules. También copiará un shelly.so al mismo directorio.

    Objetivo 5

    1. Asignamos 4 chunks cuyos notes son las variables de entorno correspondientes. El array de punteros notes tendrá 4 punteros a estos 4 notes y un puntero NULL final, que es exactamente lo que queríamos.
    2. Asigna chunks adicionales y usa la misma estrategia que usamos para el objetivo 3-4 para sobrescribir el RIP de la función main con nuestra función secreta FUN_00101e84.
      • ¿Cómo sabemos dónde está el RIP? ¿Recuerdas nuestra dirección de pila filtrada al comienzo del programa? Entra en gdb para averiguar su desplazamiento con respecto al RIP, luego suma ese desplazamiento a la dirección filtrada real para obtener el RIP.
      • ¿Cómo sabemos dónde está main? ¿Recuerdas nuestra dirección filtrada de /recipe del paso 2 del Objetivo 3-4? ¡Haz lo mismo aquí!
    3. Disfruta de tu shell :)

    El script de resolución con comentarios se proporciona en este repositorio. No dudes en contactarme si tienes alguna pregunta :)

    Descargar herramienta