15 enero 2013

Saber romper medidas de seguridad no hacen que seas hacker, al igual que saber hacer un puente en un coche no te convierte en un ingeniero de automoción

Eric S. Raymond

Y finalmente, después de esta larga pausa tras el penúltimo artículo de la saga y gracias a los pedacitos de tiempo que he podido reunir entre jálogüins, fines de semana, navidades y festividades varias, por fin, el cierre de estos seis artículos sobre infección.

No sería honesto decir que no me lo he pasado mejor escribiendo los códigos que aprovechaban estas vulnerabilidades que pensando en cómo cerrarlos. El hecho de tener que escribir algo que se escapa a la idea general de programa, en la que tienes un conjunto de instrucciones que deben satisfacer el aburrido fin de contentar al usuario, es siempre atractiva. Sin embargo, la meta ha estado clara desde el principio: más allá del divertimento individual hay un interés general que prima sobre todo lo demás por protegernos de una amenaza que es clara. Y terminaré esta saga desmontando (o al menos, enseñando a desmontar) todos los trucos que he utilizado para introducirme dentro de un binario, tomar el control y pasar desapercibido.

Este artículo se centrará principalmente en dos puntos: qué debemos hacer para protegernos de estos ataques por un lado y, por el otro, cómo detectar si hemos sido realmente atacados. Comenzaré por la primera, pues como todos sabemos -y todavía más en este mundo de jefes poco comprensivos con graves problemas a la hora de asimilar explicaciones- más vale prevenir que curar.

Medidas preventivas
Un día, cierto usuario despreocupado (y orgulloso, a la par que seguro de sí mismo usuario de una flamante Ubuntu versión algo punto diez) se encuentra desayunando tranquilamente, pensando en lo que mola Linux porque no hay virus. Pero, pobre de él, ahora hasta el vecino usa Ubuntu. Y desgraciadamente, un día, en algún espacio dedicado a noticias sobre tecnología en la tele, anuncian que ¡oh, imposible! un virus llamado Linux.RimmedGlasses.B (no, no existe realmente) amenaza la delicada sensación de dominio intelectual de nuestro querido usuario sobre los pobres no-conversos Windowsers expuestos a los peligros de la red.

Es el momento de bajar al mundo real.

Linux ha sido históricamente más seguro que Windows, pero esto es realmente una consecuencia de, entre otras, cómo se estructura el sistema y de que ciertas prácticas que en Windows favorecen mucho la infección, en Linux sencillamente no se da(ba)n. Además de que, cómo no, un sistema operativo que se usaba tan poco no era un blanco tan popular como el sistema de Microsoft.

Sin embargo, los tiempos cambian. En otros tiempos, la usual seguridad de Unix residía en que si no eres root, no eres nadie. Los usuarios que compilaban software para instalarlo en el sistema eran muy contados (en gran medida, sólo root, detrás del cual solía haber un administrador de sistemas responsable y medianamente preparado). Hoy por hoy, quien piensa en Unix piensa en Linux. Y quien piensa en Linux piensa en esa abrumadora masa de usuarios de una distribución de escritorio, susceptibles de caer en la tentación de añadir repositorios extrañísimos a las fuentes del apt, a bajarse código de todas partes, compilarlo ellos mismos y ejecutar un buen sudo make install para dejarlo a la vista de todos los usuarios (que en la práctica se reducen a un usuario con su nombre de pila en minúsculas y a root). Incluso, fíjense en lo que les digo, meter un peligrosísimo NOPASSWD en el sudoers. Dicha seguridad histórica de Linux queda en entredicho cuando introducimos al usuario corriente en la ecuación.

Esto ha abierto un montón de vías de ataque que antes, simplemente, no existían. Por eso, antes de continuar, me gustaría resumir algunas ideas que son de perogrullo, pero está bien tener en una lista aunque sea sólo por si nos olvidamos de alguna.

  1. root no es un usuario de andar por casa. Es más, no se debería poder iniciar sesión como root jamás (debería dejarse bloqueada con un asterisco en el campo contraseña de /etc/shadow) ya que esa no es la función de la cuenta de administrador. 
  2. sudo es muy cómodo, pero también es un tremendo agujero de seguridad si no se configura adecuadamente. Los tres puntos de los que nos advierte esta herramienta cuando no la tenemos configurada deberían tomarse más en serio. En resumidas cuentas, nunca configurar para que sudo no pida contraseña.
  3. Los repositorios de software (sean los del apt, yum, o los que correspondan), siempre de fuentes de confianza y firmados.
  4. El mejor binario es el que te compilas tú mismo. Eso sí, lo dicho, ojo con qué compilamos y de dónde lo sacamos. Desconfiar de cualquier enlace (web) que apunta directamente a un ejecutable. Lo mismo con los scripts.
  5. Ciertos directorios deberían poder leerse (o escribirse) sólo por root. Nunca relajar los permisos de directorios del sistema sólo porque nos sea más cómodo de administrar desde el usuario corriente.
  6. Continuando con el tema de los permisos, el permiso de ejecución (modo +x de los archivos) sólo debería estar activo para directorios y ejecutables que vengan con el sistema o hayamos compilado nosotros mismos. Tener particiones de Windows con este permiso de ejecución activado por defecto no es buena idea, debemos montar siempre con noexec.
  7. La variable de entorno PATH nunca debe tener el directorio de trabajo (.) en la lista. Nunca jamás. Los ataques companion han sembrado muerte y destrucción durante los ochenta y los noventa en los sistemas tipo DOS precisamente porque los programas se buscaban primero en el directorio de trabajo, y después en la ruta de búsqueda. Dejemos el pasado pasar.
  8. La variable de entorno LD_PRELOAD debería tener sólo aquello que sabemos para qué sirve. 

Teniendo todo esto en cuenta, y sabiendo que aquello que queremos proteger son nuestros binarios de una modificación no deseada (bonito eufemismo), hablaré un poco de las herramientas más populares destinadas a este fin, ventajas e inconvenientes. Algunas de ellas son IDS pensados sólamente en detectar qué ficheros son modificados (he obviado los diplodocus de la detección de intrusiones como OSSEC), no vamos a matar moscas a cañonazos.

bsign
bsign es una herramienta de firmado y verificación de ejecutables (ELF) mediante en espacio de usuario que funciona sobre GPG (esto quiere decir que, técnicamente, podríamos utilizar cualquier tipo de clave soportada por GPG), por lo cual vamos a necesitar una clave privada lista para este fin. Esta herramienta puede encontrarse en las principales distribuciones de GNU/Linux, es verdaderamente fácil de usar y para lo que nos interesa sólo se reduce principalmente a dos casos de uso, a saber: firmar y verificar.

Para firmar:

% bsign --sign ejecutable

Acto seguido nos pedirá la contraseña de nuestra clave privada (si la tenemos configurada, cosa que yo recomiendo), y listo, el binario está firmado con un chorro de bytes al final del mismo conteniendo la suma de comprobación cifrada de lo que acabamos de firmar.

Para verificar la integridad, bastará con llamar a :

% bsign --verify ejecutable

Yo recomiendo firmar con claves RSA en lugar de con DSA, básicamente porque es más rápido.De hecho, GPG computa firmas RSA como diez veces más rápido que DSA.

bsign devolverá un mensaje indicando el estado de comprobación, así como un valor de retorno (que podríamos examinar mediante la variable del shell $?) que será 0 si la comprobación ha ido bien, o distinto de cero -en realidad, un código de error- si ha fallado. Podemos leer la página de manual de bsig para poder darle un significado a estos códigos de error si nos interesa integrar esta utilidad en algún programa más grande.

Como ya dije, la gran ventaja de bsign es que es muy fácil de usar, su principio de funcionamiento es igualmente fácil de comprender y fácil de integrar. Los únicos fallos, si es que merece la pena mencionarlos como tales, son que sólo soporta ELF (cosa que hoy por hoy no es problemático, ya que la práctica totalidad de binarios nativos de GNU/Linux son ELF) y que es una herramienta de espacio de usuario. Es decir, no podemos proteger nuestro sistema globalmente con bsign de una forma directa, habría que llamar a bsign antes de cada ejecución.

Este último problema estuvo (que ya no está) solucionado gracias al proyecto del que hablaré a continuación. En sí no tiene gran utilidad, pero quién sabe, quizá anime a alguien a retomarlo ;)

DigSig
DigSig fue un proyecto que pretendía llevar a nivel de núcleo las comprobaciones de integridad de binarios firmados con bsig, pero que desgraciadamente dejó de mantenerse en 2009. La idea era un módulo del núcleo que a cada ejecución buscaba una firma de tipo bsig, la validaba y decidía si debía ser ejecutado o no.

He intentado compilar DigSig bajo un kernel 3.3, pero las cosas han cambiado mucho en el kernel de Linux desde 2009 (en realidad sólo lo suficiente para estropearme la compilación). De todas formas, se echa de menos algo como DigSig para este sistema operativo. Algún avispado programador debería darse cuenta de esta necesidad y retomarlo.

Tripwire
El problema de bsign es que se trata de una herramienta de firmado y verificación de ejecutables ELF que debemos ejecutar sea manualmente, sea formando parte de un proyecto más grande. Tripwire viene a proporcionar todo un sistema de control de integridad orientado a la detección en lugar de la prevención. Según su página de manual, Tripwire compara los archivos y directorios del sistema contra una base de datos previamente construida (por lo tanto, lo hace completamente inútil ante un sistema ya atacado) mediante la cual puede encontrar modificaciones, borrados, cambios en los atributos, etcétera.

El problema de Tripwire es que es un producto comercial. Sin embargo, si nuestra intención es vigilar una sola máquina (o conjunto reducido de ellas), sin control ni generación de informes centralizados, podemos descargarnos la versión libre (en código fuente y GPL) directamente de SourceForge.

Asumiendo que la compilación ha ido bien, durante la instalación nos pedirá las contraseñas con las que se protegen las claves de sus bases de datos, y llegará esa divertida fase de configuración y puesta en marcha :P

Tripwire se basa en un fichero de políticas binario y cifrado que se genera a partir de una plantilla en texto plano (que en mi sistema se encuentra en /usr/local/etc/twpol.txt), el cual contiene una lista de todos los ficheros (y directorios) que están bajo control y las propiedades (permisos, hash, fechas de modificación, etc) que no deberían modificarse. Por defecto este archivo es amplísimo, y posiblemente nos interese suprimir la mayoría de las reglas para hacer un control más centralizado. Una vez modificado este archivo, bastaría con ejecutar:

% sudo twadmin -m P /usr/local/etc/twpol.txt

Para generar de nuevo las políticas a partir de la plantilla, y:

% sudo tripwire -m i 

Para regenerar la base de datos. Nótese que twpol.txt es sólo una plantilla, no es algo que Tripwire utilice directamente, por lo que podríamos borrarlo (o moverlo a otra ubicación) una vez generadas las políticas.

Podríamos obtener un informe detallado de las violaciones de acceso sobre los archivos vigilados con:

% sudo tripwire -m c

Comando que podríamos introducir en el crontab para enviar estos informes periódicamente por correo a la cuenta de administrador, por ejemplo.

El problema de esto es que necesitamos conocer todos los ejecutables (o directorios donde se encuentran) para que Tripwire sea algo verdaderamente efectivo. Sin embargo, el hecho de poder contar con un fichero de texto en el que podamos especificar una lista de ficheros a estudiar sin tener que modificar los binarios y además poder generar informes más o menos comprensibles lo hace ya superior a bsign.

AIDE
El Advanced Intrusion Detection Environment (AIDE) se presenta como otra herramienta de tipo IDS dedicada a vigilar archivos del sistema de una manera muy similar a como lo hace Tripwire: existe un fichero con reglas para los ficheros vigilados, y una serie de herramientas para manipular y visualizar la base de datos.

La gran desventaja de AIDE respecto de Tripwire es que a diferencia de este último, AIDE no encripta la base de datos (aunque puede comprimirla). Sin embargo, ganamos a cambio mayor facilidad a la hora de instalarlo y ponerlo en marcha, ya que, por ejemplo, no hace falta compilar el equivalente del fichero de políticas.

De nuevo, tenemos dos casos de uso principales, inicializar la base de datos:

% sudo aide -c ruta/al/fichero/de/configuracion.conf --init

Y generar un informe:

% sudo aide -c ruta/al/fichero/de/configuracion.conf --check

El cual podríamos generar periódicamente y enviar por correo de la misma manera que haríamos con Tripwire.

Hay más comandos, como comparar dos bases de datos, actualizar la existente, etc. Un aspecto interesante de AIDE es que puede trabajar (y de hecho, espera trabajar) por separado con dos bases de datos diferentes. Esto nos podría permitir tener una base de datos de referencia en un sistema de ficheros de sólo lectura mientras podríamos generar una base de datos con la situación real del sistema en otra ubicación, pudiendo comparar ambas fuera de la máquina, etcétera.

Mi humilde opinión
Yo soy, personalmente, partidario de soluciones a nivel de kernel tipo DigSig. AIDE, Tripwire y bsig sólo nos van a avisar si los archivos bajo vigilancia han sido comprometidos. Y puesto que no podemos hacer un chequeo completo de todo el sistema cada dos por tres, habrá una ventana de tiempo entre la infección y la verificación en la que nuestro atacante podría haberse hecho con información valiosísima. Por ejemplo, supongamos que el código infectado busca tocar ssh o las propias bibliotecas pam y monitorizar el teclado. El daño ya estaría hecho si durante ese tiempo consiguió hacerse con alguna clave importante y enviársela al atacante. ¡Sería incluso peor si hablamos de un sistema con varios usuarios!

Ya que DigSig está en coma, y lo demás me deja el miedo en el cuerpo, yo ofrezco la siguiente idea: que el binario se autoverifique al principio. Si las cosas no encajan, el binario rechaza ejecutarse, lanza un aviso y fin de la amenaza. En función del binario, esto podría tener un efecto más o menos catastrófico en el sistema, pero al menos sabríamos que hay un problema antes de la propia explotación del mismo.

La prueba de concepto se llama spintop: un archivo fuente en C que compilado con el resto de un proyecto (esperablemente mayor) se encargará, antes de ejecutarse el main, de comprobar que hay una firma válida tipo bsign en el binario desde el que se ejecuta. A la hora de incluir esto en un proyecto, debemos tener en cuenta que:

a) Hay que definir una macro SPINTOP_TRUSTABLE_FINGERPRINT con una cadena conteniendo el fingerprint en mayúsculas de la clave con la que firmamos y
b) Que hay que que compilar contra GPGME, preferiblemente contra la versión estática de la biblioteca.

He intentado escribir esta prueba de concepto con la menor cantidad de llamadas a funciones externas posible (aunque me ha quedado malloc en el tintero) y definiendo todas las funciones como static. Con todo esto, consigo que la cosa funcione incluso para binarios que no dependan de la biblioteca estándar de C (cosa rara, pero nunca se sabe) y sobre todo, para complico a un posible atacante el asunto de hookear las llamadas que spintop podría necesitar para alterar su funcionamiento. Siendo un código más o menos "autónomo", el manipular spintop se hace más difícil. Evidentemente, esto requiere un estudio mucho más serio.

Cuando se compila un binario con spintop y no hacemos nada más, recibiríamos un mensaje como el siguiente:


% ./myprogram 
spintop warning: binary is not signed (yet). Use bsign(1) to generate a signature before running it.
%

Es normal, ya que no lo hemos firmado. Suponiendo que tenemos bsign configurado, sólo nos quedaría firmarlo:


% bsign --sign ./myprogram

Enter pass phrase: ***
%

Y al ejecutar todo iría sobre ruedas. Aunque bueno, eso ya dependería del binario en cuestión :P


% ./myprogram             
LOOK MOM, I'M A PROGRAM AND I'M DOING THINGS LIKE ASKING
THE USER FOR STRINGS WITH gets() HURRR DURRR AM I COOL YET?
GIMME YOUR NAME: fulanito
LOL K, YOUR NAME IS fulanito
%

Si nos atrevemos a modificar un byte, sólo un byte, incluso añadiendo algo al final sin tocar el resto:

% echo -n a >> myprogram 
% ./myprogram        
spintop error: MODIFIED BINARY DETECTED (Bad signature)
spintop error: SOMEBODY CALL DE COPS
%

Y habremos prevenido la ejecución de algo potencialmente peligroso. Eso sí, si nuestro código malicioso de ha enganchado al punto de entrada, no habremos podido evitar la ejecución del mismo, pero al menos habremos impedido que manipule el resto del programa en tiempo de ejecución.


Y para terminar, hablemos de estadística
En los artículos anteriores hemos revisado diversas técnicas de infección. Hemos visto que, como todo en esta vida, tiene sus putos fuertes y sus puntos débiles. En general, nuestros métodos de infección tocaban partes del ejecutable que, si bien no son críticas o al menos su modificación se hacía de forma que pasase desapercibida, hacía del ejecutable infectado algo particularmente especial. Por ejemplo, la información de PT_NOTE suele ser prescindible, pero es muy raro encontrar un binario que no la defina.

Nuestra meta en este punto es evaluar hasta qué punto estos binarios "especiales" se dan realmente en un sistema Unix, y justificar la existencia de una heurística que permita aventurarnos a alertar de la posibilidad de una infección. La idea es revisar todos los métodos de infección y activación que hemos visto hasta ahora, y averiguar si realmente dichas modificaciones pueden pasar desapercibidas, ignorando por completo el código que hayamos inyectado.

Para esto, revisaremos la estructura de algunos miles de binarios ELF ya no de una sola arquitectura (como llevamos haciendo con las pruebas de concepto hasta ahora), si no de unas cuantas más para situarnos en un escenario un poco más realista, y de paso, gozar de más y más variados ficheros. Los sistemas donde se ha realizado este estudio han sido:

- Ubuntu 12.04, i386 (9160 binarios)
- Ubuntu 12.04, x86_64 (2045 binarios)
- Debian Squeeze, x86_64 (941 binarios)
- Solaris 10, UltraSparc IIe (12163 binarios)
- Debian testing, x86_64 (1825 binarios)
- Debian Sequeeze, i386 (3152 binarios)
- Ubuntu 10.04, i386 (2442 binarios)

El análisis ha sido automatizado gracias a una pequeña herramienta (elfprobe) que escribí expresamente para la tarea, las fuentes se pueden descargar aquí.

La sobreescritura PT_NOTE: algunos datos
Los ejecutables infectados mediante esta técnica se caracterizan por compartir las siguientes características:
  1. Perdieron su segmento PT_NOTE
  2. Tienen (normalmente) tres (o más) segmentos PT_LOAD
  3. Tienen segmentos ocultos

Y digo "normalmente", porque si el binario original tiene un solo segmento, esto deja de ser verdad. En los sistemas analizados, se ha contabilizado un total de 79 binarios con un único segmento PT_LOAD. No es una situación común, pero se podría utilizar para ocultar una infección.

Los binarios que no tienen PT_NOTE son mucho más numerosos: en Solaris la práctica totalidad de ellos carecen de esta característica (12110) mientras que en el resto de sistemas suman 4452, encontrándose la mayoría en las Ubuntu de 32 bits. Estas cifras no son nada despreciables.

Por otro lado, la cifra de binarios que tienen más de dos PT_LOAD asciende a 388, encontrándose, una vez más, la mayoría en Solaris. Los que tienen más de dos PT_LOAD y ningún PT_NOTE son algunos menos, 378, pero la cifra anda muy a la par. En el caso particular de los sistemas Linux, sólo se encontró un fichero (en la Ubuntu de 32 bits) que además pertenecía a otra arquitectura.

El problema de los segmentos ocultos, si es que de verdad es un problema como tal, es una constante de todas nuestras técnicas de inyección: el código infectado no aparece dentro de ninguna sección y eso puede ser bueno y malo a la vez. Es bueno, porque herramientas como objdump no desensamblan lo que no está dentro de una sección. Y es malo, porque un binario no suele tener segmentos no cubiertos por ninguna sección. Puesto que este problema es compartido por la técnica de desplazamiento de cabeceras, analizaremos este fenómeno más adelante.


Desplazamiento de cabeceras

En esta técnica la detección adquiere un nuevo orden de complejidad. Lo único que caracteriza a estos binarios es:
  1. La tabla de cabeceras de programa ha sido desplazada.
  2. Hay segmentos ocultos.
Desde el punto de vista de la detección, la dificultado reside en que no hay una forma de afirmar con total seguridad que la tabla de cabeceras de programa ha sido desplazada. En Linux, esta tabla de cabeceras suele estar antes del contenido de la primera sección (empezando desde el principio del fichero), pero podría estar en cualquier otra ubicación. En todos los sistemas analizados no he encontrado ningún binario (salvo los que yo he infectado deliberadamente) que tenga la cabecera de programas después del comienzo de la primera sección, por lo que esto podría ser una buena heurística.

El gran problema de la inyección: los segmentos ocultos
Como hemos visto antes, los segmentos inyectados no suelen aparecer como secciones del programa. Por supuesto que se podría modificar el binario para que esto no fuese así, pero añadiríamos una dosis de complejidad extra que a la larga nos sería contraproducente. Sin embargo, esta decisión tiene sus consecuencias, y es que estos datos "no cubiertos" son bastante fáciles de localizar.

De todos los binarios analizados, sólo se encontró un binario no infectado y con segmentos ocultos, por lo que esto podría ser una heurística bastante buena.

Inyección segmentada: virtualmente imposible de detectar
Tal cual. La inyección segmentada se basa en aprovecharse de unos huecos de alineamiento entre funciones que bien pueden no existir. Y escribir una heurística sobre qué binarios tienen o dejan de tener estos espacios es muy similar al código que escribí varias entregas antes sobre la viabilidad de esta técnica. La única forma de detectar un binario infectado por esta técnica (sin ejecutarlo, claro) es, o bien buscando partes del segmento de código que no estén cubiertas por la sección .text (algo que es posible que nunca encontremos ya que, a pesar de que en nuestras técnicas no lo hemos modificado nunca, tocar esa información es trivial), o por alguna vulnerabilidad del mecanismo de activación. Y esto nos lleva al siguiente punto:

Modificando el punto de entrada: no tan malo al fin y al cabo
Cuando vimos esta técnica de activación hemos asumido que es la más simple y que suele ser la más vigilada. Sin embargo, una vez infectados, ¿hasta qué punto podemos sospechar?

Pues resulta que el método depende de la robustez de la técnica de infección que haya detrás. Una forma sería buscar si el punto de entrada cae en una dirección que no esté cubierta por ninguna sección. Esto en el caso de la infección segmentada bien hecha, modificando .text para cubrir la rutina de reensamblado, puede llevarnos a un callejón sin salida. Sin embargo, en el caso de las otras dos técnicas, es más fácil debido al problema de los segmentos ocultos.

De todos los sistemas analizados, se han encontrado 4544 binarios donde el punto de entrada caía en una dirección no cubierta por ninguna sección, todos ellos en Solaris. En el resto de sistemas Linux, sólo se encontró un sistema Linux con esta peculiaridad.

Conclusión: para la plataforma que nos atañe en este post, modificación del punto de entrada + infección segmentada => potencialmente indetectable. Para todo lo demás, basta con comprobar dónde cae el EP.


.got.plt: complicado de comprobar, pero no tanto

La única complicación real aquí es técnica: se trata de encontrar una sección que se llame ".got.plt" y analizar su contenido. Tal análisis se resumirá en comprobar que los punteros caigan en siempre en la .plt, que es donde le corresponde. Nótese que de momento sólo puedo asegurar que este truco funciona en GNU/Linux para x86 de 32 y 64 bits con binarios construidos por GCC, en Solaris (al menos en mi Sparc) la sección ".got.plt" simplemente no existe (los punteros son modificados directamente dentro de la .plt, y sí, la .plt está en un segmento con todos los permisos de acceso encendidos).

Para el resto de sistemas, sólo se encontraron dos binarios (y en la Ubuntu de 32 bits) que tenían una .got.plt con entradas fuera de la .plt: uno era busybox (el formato interno de este binario es una auténtica rareza, al menos en mi sistema, no me preguntéis por qué), y el otro era la propia libc, con cuatro entradas fuera. En el resto de sistemas Linux, todo estaba en orden. La heurística que podríamos derivar de aquí no es perfecta, pero algo es algo.

.plt: confiando en que el compilador haga las cosas bien
Este caso es verdaderamente complicado de detectar, ya no sólo por el simple hecho de que puede haber muchísimos compiladores diferentes que generen .plts con formatos cualesquiera, si no por la propia variedad de .plts que nos podemos encontrar dentro del conjunto de binarios generados por el mismo compilador. Superadas estas complicaciones, el algoritmo es siempre el mismo: comprobar que las direcciones e índices de los símbolos colocados por los wrappers de la .plt siguen un orden creciente con el mismo salto.

Este truco sólo ha funcionado parcialmente en x86 (32 y 64 bits). En Solaris las entradas de la .plt los saltos no tienen un orden claro. En el resto de los sistemas de estudio he encontrado varios binarios cuyas .plts son bastante extrañas (busybox una vez más, el binario de skype e incluso ld), sin embargo siguen siendo la minoría. De las .plts soportadas, ninguna tenía entradas fuera de orden.

Conclusión: hacer una heurística sobre esto es posible, pero hacerla perfecta es bastante complicado. Si algún software quiere aprovecharse de esta técnica de activación, será difícil hacer una detección temprana de la infección.

.ctors: además de peligroso, evidente
Y por último, le toca el turno a .ctors. Hemos visto que esta técnica de infección es muy arriesgada, y en la práctica será todavía peor: además de funcionar sólo con los binarios generados por GCC, estamos cargándonos el marcador inicial (-1) que caracteriza esta sección. Esto hace la detección algo trivial.

Como era de esperar, ningún binario en todos los sistemas analizados tenía una sola sección .ctors que no empezase por -1. Por tanto, podemos hablar igualmente de buena heurística. De todas formas, estaría bien completar esta heurística comprobando si este entero que reemplaza al marcador es verdaderamente una dirección y estudiando dónde cae.

Conclusión de conclusiones
Este pequeño estudio ha arrojado algunos resultados interesantes: al final, las técnicas más simples acaban siendo las más robustas para esconderse de la mirada inquisitiva de la heurística de un antivirus. Las mejores, evidentemente, siguen siendo las más complicadas. ¿Se pueden escribir virus para Unix? Sí. ¿Se puede hablar de una amenaza? Potencialmente. ¿Nos podemos proteger? Afortunadamente sí. Sin embargo, la estadística cantará y cuando llegue ese futuro en el que escribir virus para Linux se vuelva algo divertido algún administrador se llevará un buen disgusto tarde o temprano.

Esta extensa saga ha obviado técnicas menos directas para acercanos a los detalles de infección directa de binarios, pero existen formas más simples (e incluso más efectivas) con las que se podrían conseguir resultados mucho más dañinos. Una forma sería, por ejemplo, modificando los Makefiles: encontrando la sección install y añadiendo la ruta de nuestro código malicioso podríamos ser ejecutados con permisos de root en el futuro, o incluso añadiendo alias a sudo en el .bashrc y cosas similares.

La picaresca está ahí, sólo esperemos que no la exploten demasiado.

No suelo añadir sección de agradecimientos a los artículos, pero después de la paciencia que tuvo Yago con esta interminable saga, de los ánimos de knx y NighterMan, y de la colaboración de n0p y también de la hermana de Pales al dejarme probar cositas en sus sistemas, toda esta gente bien se merecía una línea. Y por supuesto, a todos aquellos que tuvieron el estómago de llegar hasta aquí. Gracias.


Update: A fecha del 16 de enero de 2013, día más tarde de publicar este artículo, alguien sacó un parche para realizar verificación de firma digital a nivel Kernel. Información: https://twitter.com/2gg/status/291483726410559488



Artículo cortesía de BatchDrake

4 comments :

Ksha dijo...

@BatchDrake la verdad es que te falto uno muy importante en cualquier sistema linux, la herramienta es audit y la verdad que en conjunto con tripware hacen muy buena mezcla. Excelente articulo me los lei todos

Adrián Ruiz Bermudo dijo...

Enhorabuena por esta saga tan intensa y repleta de inquietudes y técnicas que aportan mucho a la comunidad.

Y bueno, una par de apuntes: cuando hablas de OSSEC y lo de matar moscas a cañonazos... ¿por qué lo dices? Lo uso y creo que es un sistema muy completo y lo mejor, que lo mantienen. ¿Quizás te refieras a que al ser un IDS multipropósito no entraba en los candidatos de notificar la integridad de archivos..?

También comentas el uso de PAM para monitorizar el teclado, ¿puedes aclarar eso y facilitar alguna referencia?



gracias y saludos,

Gonzalo J. Carracedo dijo...

Te he respondido y el comentario ha desaparecido xD o sea que voy a hacer backup antes de responderte.

Primero que todo, gracias :)

Resumiendo lo que venía a decir antes, precisamente, OSSEC es completo, demasiado completo. Mi intención era estudiar las herramientas más especializadas en el problema de la integridad. Poner OSSEC en marcha y a punto va más allá de configurar las cuatro cosas que tienen Tripwire o AIDE, y explicar todo esto llevaría una saga entera para él solito.

Sobre lo de pam, decir que ahí me colé yo. No es tanto monitorizar el teclado como loguear contraseñas: enchufándonos al pipe que une a pam_unix.so y unix_chkpwd uno puede hacerse fácilmente con un buen puñado de contraseñas de todos los procesos de autentificación que pasan por ahí. ¿Referencias? La propia implementación de estos programas. Por ejemplo, unix_chkpwd: http://nesl.ee.ucla.edu/fw/han/old_machine_backup/overo-oe/tmp/work/armv7a-angstrom-linux-gnueabi/libpam-1.1.1-r0/Linux-PAM-1.1.1/modules/pam_unix/unix_chkpwd.c <-- hookeando funciones de lectura de ficheros o redirigiendo descriptores las contraseñas podrían pasar por algún "intermediario".

La idea realmente es mostrar que, incluso si el malware alcanza privilegios de administrador, podemos protegernos del mal uso que se le dé a estos privilegios.

Gonzalo J. Carracedo dijo...

Hola, y gracias por tu respuesta :)

Pues mira, tienes razón. Audit es especialmente interesante porque a diferencia de las soluciones que mencioné antes, este además funciona a nivel de kernel. De hecho, es muy posible que revise este artículo en un futuro próximo y acabe hablando también de esto. Te citaría, claro.