Dia 12
Capitulo 7 - Auditing program binaries
-
Un programa de software es solo tan seguro como su componente menos seguro.
-
A veces la falta de seguridad podría ser culpa de una librería de código cerrado.
-
Reverse engineering al rescate: nos permite auditar el código para encontrar:
- prácticas seguras de programación.
- vulnerabilidades
Un acto de fe
Cada vez que instalas un programa: un salto al vacío lleno de fe:
-
En que el autor no creó ese programa para dañar tus recursos a propósito.
-
En que el autor no creó ese programa para dañar tus recursos sin quererlo. (estos son los casos más difíciles de detectar). Heartbleed?
Ejemplo
-
Programa para desplegar imágenes en que no se han tomado las debidas medidas de programación segura.
-
Cargas una imagen que ha sido maliciosamente creada para explotar el programa y boom, el programa comienza a ejecutar código dictado por el contenido de la imagen, creando un conexión que permita a los malos apoderarse de tu compu.
¿Qué es una vulnerabilidad?
-
En un bug o fallo en un programa que compromete la seguridad del programa y en ocasiones del sistema que lo está ejecutando.
-
Muchas veces las vulnerabilidades se deben a código que recibe datos del mundo externo: command line parameters, un archivo de input, datos recibidos a través de la red.
-
Idea básica para aprovechar la vulnerabilidad: alimentar al programa mongo con input para el cuál el creador del programa no tomó las debidas precauciones, con el propósito de desviar el programa de su flujo normal.
- crashear el programa
- tomar control del programa y hacer que ejecute el código establecido por el atacante.
Veamos algunos ejemplo de los bugs que pueden resultar en vulnerabilidad de programa.
Stack overflow
-
Una de las vulnerabilidades más comúnes. Ranked #3 in 2011 CWE/SANS Top 25 Most Dangerous Software Errors (http://cwe.mitre.org/top25/index.html)
- las primeras dos son: SQL injection y OS Command Injection
-
Toma ventaja de que ciertas funciones para input en C no realizan verificaciones de bounds checking.
-
Bounds checking: ¿es el espacio reservado para una variable suficientemente amplio para la cantidad de datos voy a almacenar?
-
Si no realizas tal verificación, expones a que los datos que desbordan la cantidad reservada escribirán sobre el contenido de otras informaciones que le siguen en memoria: otras variables, return address. etc.
Ejemplo: digamos que en una función se definen las siguientes variables:
int counter;
char string[8];
float number;
y usamos scanf
o gets
para pedir al usuario que entre el valor para el string. El usuario entra un nombre de más de 16 caracteres. La parte más nociva es la que tachó al return address. El atacante puede redirigir el flujo del programa a código nocivo.
Si te interesan este tipo de explotaciones
- Para instrucciones y ejemplos sobre como tomar ventaja de vulnerabilidades en stack, etc:
-
Comienza con ejemplos básicos que dependen de ejecutar instrucciones directamente en el stack. Idea general, corromper el return address para redirigir el flujo a otras instrucciones que hemos puesto en el stack (como parte del string venenoso.)
-
Sin embargo, la mayoría de los sistemas operativos modernos no permiten que los programas ejecuten instrucciones directamente del stack (Non-Executable Stack). Marcan las páginas de memoria donde residen tales regiones como "no-ejectuables"
Return to libc
-
Una forma simple de aprovecharse del stack overflow que no depende de un stack ejectuable es la técnica llamada return to libc.
-
En esencia: corromper la dirección de retorno para que el programa brinque a una función de la librería libc de nuestra preferencia.
-
Pero, ¿cuán nocivo puede ser eso? Mucho, pues existen funciones en libc que pueden ejectuar otros ejectuables.
-
Ejemplo:
int system(const char *cmd);
- This function runs the command or program specified by cmd
system("\bin\sh");
abre un
-
Return to libc: http://seclists.org/bugtraq/1997/Aug/63, http://insecure.org/sploits/non-executable.stack.problems.html
Lab de hoy: return to libc
Auditando y buscando posibles stackoverflows:
Buscar:
-
funciones con un tamaño de stack grande ¿en dónde podemos detectar esto?
-
invocaciones a funciones de input o copia de memoria: gets, scanf, strcpy, memcpy
-
la función que esta invocando a la función de input vulnerable, ¿está conciente del espacio disponible?
- lo recibe como argumento?
- lo verifica internamente?
Ojo:
-
El compilador pudo haber implementado algunas de las funciones ofensoras de forma intrínseca (en vez de crear la invocación).
-
En cualquier momento en que tengamos un loop sobre una variable local: causa de preocupación.
Stack checking (previniendo stack overflows)
- Stack canary (a.k.a stack cookie) - compilador inserta código cerca del prólogo y epílogo de la función:
- prologo: inserta un valor random en el stack frame
- epilogo: verifica que el valor no ha sido modificado, si lo fué, llama a excepción
#include <stdio.h>
int main(int argc, char *argv[]) {
char st[8];
gets(st);
printf("%s\n",st);
return 0;
}
Disassembly del ejemplo:
00000000004005ed <main>:
4005ed: 55 push %rbp
4005ee: 48 89 e5 mov %rsp,%rbp
4005f1: 48 83 ec 20 sub $0x20,%rsp
4005f5: 89 7d ec mov %edi,-0x14(%rbp)
4005f8: 48 89 75 e0 mov %rsi,-0x20(%rbp)
4005fc: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax ;// rand value read
400603: 00 00
400605: 48 89 45 f8 mov %rax,-0x8(%rbp) ;// cookie writen
400609: 31 c0 xor %eax,%eax
40060b: 48 8d 45 f0 lea -0x10(%rbp),%rax
40060f: 48 89 c7 mov %rax,%rdi
400612: e8 d9 fe ff ff callq 4004f0 <gets@plt>
400617: 48 8d 45 f0 lea -0x10(%rbp),%rax
40061b: 48 89 c7 mov %rax,%rdi
40061e: e8 8d fe ff ff callq 4004b0 <puts@plt>
400623: b8 00 00 00 00 mov $0x0,%eax
400628: 48 8b 55 f8 mov -0x8(%rbp),%rdx ;// stack cookie read
40062c: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx ;// and compared
400633: 00 00
400635: 74 05 je 40063c <main+0x4f>
400637: e8 84 fe ff ff callq 4004c0 <__stack_chk_fail@plt>
40063c: c9 leaveq
40063d: c3 retq
40063e: 66 90 xchg %ax,%ax
-
The cookie is read from somewhere in memory where the OS is keeping certain data structures.
-
Existen exploits para los canaries http://phrack.org/issues/56/5.html
Abusando del heap - Heap overflows
-
Se aprovecha que de la forma en que está organizado el heap (al menos sus bloques vacios). (como un double linked list).
-
Al ahogar una entrada del heap puedes afectar los campos que apuntan a los vecinos. De esta forma cuando se borra el nodo afectado, podrías tener acceso a escribir a la memoria que escojas.
-
http://www.mathyvanhoef.com/2013/02/understanding-heap-exploiting-heap.html
Filtros para strings.
Si la causa de estas vulnerabilidades son los strings. ¿Por qué no poner un filtro que dificulte la entrada de código malicioso? Por ejemplo, convertir el string a Unicode cambiaría cada byte en 2 bytes donde el segundo es 0x00.
Los atacantes se las han ingeniado hasta para producir secuencias que garanticen abuso aun en presencia de filtros: http://bit.ly/4995-unicode-proof-shellcodes
Overflow de enteros
-
Tratamiento incorrecto con variables de tipo entero puede resultar en overflow numérico y eventualmente en buffer overflow.
-
Caso más común en cuando recibe el largo de un bloque de datos.
-
Si no se verifica adecuadamente podríamos estar dejando pasar valores que nos puedan causar problemas.
-
Ejemplo 1: Signed buffer comparisons are dangerous
signedBufferLen <= MAX_LEN
es cierto cuando0 <= signedBufferLen <= MAX_LEN
pero también cuandosignedBufferLen < 0
. -
Ejemplo 2:
c foo(char *content, uint size) { // crear espacio para contenido más un header. char *a = malloc(size + 0x18); strcpy(a+18, content); ... }
-
El problema es que estamos aceptando un largo y no estamos validando si puede desbordar el dato. Por ejemplo, si
size = 0xfffffff8
, reservará solo 0x10 y tratará de grabar allí un string de largo 0xfffffff8. -
Solución: usar bien los tipos. En este caso, no es lo mismo tratar el largo como 32-bits
-
Moraleja: Si se acerca a desbordar el tamaño del word, mejor decláralo de un tamaño menor para que no pierdas el bit más sig.
-
-
Moraleja general: Toda variable que se use para llevar cuenta de una largo, declarala como unsigned.
Caso de la vida real de vulnrabilidad en Windows 2000
-
On July 19, 2001, more than 359,000 computers connected to the Internet were infected with the Code-Red (CRv2) worm in less than 14 hours. It attacked computers running Microsoft's IIS web server.
-
The cost of this epidemic, including subsequent strains of Code-Red, is estimated to be in excess of $2.6 billion.
Causas:
-
idq.dll
versions 4 and 5 of Microsoft IIS. -
Internet Services Application Programming Interface
-
If URL ended in
.idq
or.ida
it was sent to the functions in idq.dll The ida functions did not contain bounds checking conversion from ASCII to Unicode (especially bad because the Unicode will take twice as much characters). -
Esquema de la función a la que eran dirigidos los URL si terminaban en *.ida:
AddExtensionControlBlock(char *st)
. . .
push the address of an exception function to the stack
(in case something goes wrong)
sub esp, 0x1d0 <- un stack frame ‘grande’
para aguantar variable localSt
. . .
computa el largo del string st
lo compara con 0x190 (400 en decimal)
si es mayor llama a una excepción
. . .
llama a DecodeURLEscapes(char *st, unsigned int len, char *localSt,…)
llama a DecodeHtmlNumeric
**otras cosas***
DecodeUrlEscapes(char *st):
for each char in st:
convert to Unicode, append in st.
Esquema de exploit
- Más complicado que return to libc pues la función ha puesto otras informaciones en el stack que son accesados durante la ejecución (si los corrompes, invocarás al exception handler)
[string ] <— overflow this!
[saved bp ] <— overflow this!
[address of exception handler] <— contaminate with address to your shellcode
[return address ]