¿Por qué aparece "No se puede encontrar el límite de la función actual" cuando sobrescribo la dirección ret de un programa vulnerable?

3

Quiero explotar un desbordamiento de búfer basado en pila para fines educativos. Hay una función típica llamada con un parámetro de main que se proporciona como entrada del programa y un búfer local donde se guarda el parámetro. Se proporciona una entrada tal que nops + shellcode + address_shellcode lo explotaré. Después de la depuración con gdb encontré la dirección del código shell como pasará como un parámetro y justo después de que strcpy examine la pila y el $ ebp + 8 que es la dirección de retorno ha sido exitoso sobrescrito con la dirección del shellcode. Así que tengo lo que quiero. Pero cuando me adelanté a la ejecución obtuve:

->shellcode_address in ?? ()

y luego

Cannot find bound of current function

La dirección de retorno tiene el valor que quiero. Alguna idea de lo que esta pasando? También cuando lo ejecuto tengo un error de segmentación y lo he compilado con -g -fno-stack-protector

Aquí está el código:

void echo(char *s, unsigned int length, long int a, short b)
{
  unsigned char len = (unsigned char) l;
  char errormsg[] = "bla bla bla\n";
  char buf[250] = "You typed: ";

  strcat(buf+11, s);


  fprintf(stdout, "%s\n", buf);



int main(int argc, char **argv)
{
  gid_t r_gid, e_gid;

  /* check arguments */
  if (argc != 2) {
    fprintf(stderr, "please provide one argument to echo\n");
    return 1;
  }


  /* clear environment */
  clearenv();
  setenv ("PATH", "/bin:/usr/bin:/usr/local/bin", 1);
  setenv ("IFS", " \t\n", 1);


  /* temporarily drop privileges */
  e_gid = getegid();
  r_gid = getgid();
  setregid(e_gid, r_gid);


  /* call the echo service */
  echo(argv[1], strlen(argv[1]), 0xbccb3423, 323);

  return 0;
}

Descubrí con gdb que si sobrescribes 309 bytes, sobrescribirás exactamente la dirección de retorno con los últimos 4 bytes de tu entrada, que es exactamente lo que queremos. Así que ya que el código de shell es de 45 bytes, queremos algo como: \ x90 x 260. "shellcode". Dirección de 4 bytes (260 + 45 + 4 = 309)

Para encontrar la dirección del primer parámetro de la función, ejecuté varias veces gdb con una cadena de 309 bytes de entrada y la dirección siempre fue la misma: 0x5ffff648

Entonces, si agrego una dirección (orden inverso, es decir, 0xabcdefgh - > \ xgh \ xef \ xcd \ xab) que es más alto donde el parámetro apunta, el procesador caerá en un comando NOP, sin hacer nada hasta que llegue el shellcode Termino con esto: r perl -e 'print ("\x90" x 260 . "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh" . "\x3e\xf8\xff\x5f")'

    
pregunta curious 05.01.2012 - 18:13
fuente

1 respuesta

3

La memoria es solo una gran secuencia de bytes. Cuando gdb quiere mostrarle "dónde" está, le gusta adivinar de qué "función" forma parte el código de operación actualmente ejecutado, para que pueda escribirlo ("usted está en main (), línea 17"). Para hacer eso, gdb debe usar alguna información adicional, como:

  • tablas de símbolos, que indican dónde comienza cada función en el archivo ejecutable y posiblemente su tamaño;
  • información de depuración (agregada por gcc con la marca '-g');
  • análisis heurístico del fragmento de código, para detectar los distintivos de la función clásica prólogo (" push %ebp; movl %esp, %ebp ", también conocido como 0x55 0x89 0xE5) y final (" ret ", 0xC3).

Su "código de shell", cuando está en la RAM (en la pila), no está en una parte que se asigna desde el archivo ejecutable (está en la pila, en su lugar) y, de todos modos, no fue visto por el compilador, por lo tanto, no está cubierto por tablas de símbolos ni información de depuración. También es un fragmento de código bastante atípico, sin prólogo (el prólogo es acerca de la preparación de la pila para que se pueda recuperar ese argumento y la pila se limpie al salir) y tampoco tiene final: el "código de shell" hace un execve() Llamada al sistema, por lo que no le importa mantener la pila limpia o nunca volver. Por lo tanto, no es de extrañar que gdb no pueda encontrar dónde se supone que comience o termine la "función" en la que saltó.

Tu falla de segmentación es otra cosa. Mi conjetura es que las páginas que contienen la pila están marcadas como no ejecutables, por lo que al saltar al "código de shell", las trampas del núcleo, y mata el proceso ofensivo. Hay varios mecanismos de protección relacionados con los desbordamientos de búfer en un sistema Linux (supongo que está utilizando Linux):

  • La pila se puede marcar como "no ejecutable". En los procesadores x86 de 32 bits, esto se puede lograr con segmentos (un remanente de tiempos anteriores) o a través de la MMU (ya sea con el bit NX , en las máquinas que lo admiten, o con algunos TLB ninjitsu ), o ambos.

  • Aleatorización del diseño del espacio de direcciones modifica las direcciones de los diversos elementos de una aplicación, de forma aleatoria, con cada ejecución. Esto hace que sea más difícil para el atacante adivinar cuál será el valor que desea almacenar (a través de un desbordamiento de búfer) sobre la ranura de "dirección de retorno". La pila no ejecutable significa que el exploit tendrá que saltar a algún fragmento de código existente (por ejemplo, el código libc), no a algún emplazamiento en la pila; ASLR mueve a libc para hacer que sea un blanco difícil.

  • El código generado por el compilador puede incluir garantías contra la aceptación del desbordamiento del búfer. Las versiones recientes de gcc generarán un código oculto adicional que verifica si se produjo un desbordamiento del búfer antes haciendo el fatídico ret . Básicamente, un valor aleatorio "canario" se almacena en la entrada de la función justo antes de la ranura de "dirección de retorno"; un desbordamiento de búfer de una variable local, para tocar la dirección de retorno, tendría que "repasar" el canario y (con alta probabilidad) cambiará su valor. El código generado por gcc comprobará el canario y abortará el proceso si su valor ha cambiado, antes de regresar de la función.

Al compilar con -fno-stack-protector , desactivas el código canario (gcc no incluyó el código canario en el ejecutable producido). Al utilizar sysctl -w kernel.randmoize_va_space=0 , desactiva ASLR (en toda la máquina). Mi conjetura es que el primer sistema (páginas de pila no ejecutables) todavía está activo en su máquina, por lo tanto, el segfault. Puede intentar desactivar el manejo de NX-bit para la pila de su ejecutable usando el comando execstack (no parece para ser parte de una instalación habitual de Ubuntu; instale el paquete "execstack" para obtenerlo).

    
respondido por el Tom Leek 06.01.2012 - 18:20
fuente

Lea otras preguntas en las etiquetas