Vous êtes sur la page 1sur 68

i

Ingenierı́a Inversa para binarios 1

Gema Gómez, Marisa Gil

Departament d’Arquitectura de Computadors, Universitat Politécnica de Catalunya


c/ Jordi Girona, 1-3, Edifici D6 Campus Nord, 08034 Barcelona, Spain

e-mail: ggomez@escert.upc.es, marisa@ac.upc.es


UPC–DAC–2003–X
23 de enero de 2003

ABSTRACT: La seguridad informática es un reto para los profesionales del sector


que luchan contra el desconocimiento de los usuarios del funcionamiento real interno de
los entornos que utilizan. La aparición de virus y gusanos masiva de los últimos tiempos,
es debida principalmente a la extensa utilización de la tecnologı́a por parte de gran parte
de la población; y la automatización en la detección de código malicioso se hace casi
imprescindible para el buen funcionamiento de la comunidad informática.
El análisis automático trajo consigo el desarrollo de técnicas de ocultación de código
malicioso para evitar ser detectado, y resultar mucho más nocivo. Si un antivirus no
puede detectar un virus, éste último puede hacer mucho más daño. Si un atacante puede
dejar un programa en una máquina con la garantı́a que nadie será capaz de ver qué hace,
su trabajo se hace mucho más relajado y sencillo.
Nosotros creemos que si un código debe ser entendido en tiempo de ejecución por un
procesador, también debe ser analizable por un antivirus, y este proceso se puede hacer
automáticamente. De una afirmación tan simple como la anterior surgió la idea de este
trabajo, que se centrará en entornos de ejución Linux sobre máquinas de 32 y 64 bits,
y en cierto tipo de cifrado concreto, cuyo código tiene la caracterı́stica de detectar si
se está ejecutando sobre el procesador directamente o con la ayuda de un depurador, y
aborta la ejecución en esta última situación.
Resolviendo este caso, la automatización de este tipo de análisis –que hasta ahora
eran manuales– está ligeramente más próxima.

KEYWORDS: binario, ELF, cifrado, Ingenierı́a Inversa, ataque, análisis, IA-32,


IA-64, montador, cargador, UPX, Burneye

1 Este trabajo ha sido financiado con la ayuda del Ministerio de Ciencia y Tecnologı́a de España y por la Union
Europea (FEDER) bajo el contrato TIC2001-0995-C02-01. Las máquinas basadas en tecnologı́a Itanium han sido
cedidas por HP/Intel al Departamento de Arquitectura de Computadoras.
ii
Índice general

1. Introducción 1
1.1. Máquina comprometida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.1. Atacantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2. Motivos de ataque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.3. Modus Operandi del atacante . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.1.4. Detección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2. HoneyPots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3. Recogida de evidencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3.1. Archivos de Registro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.3.2. Kernel y módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.3. Aplicaciones en ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.4. Binarios existentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4. Ingenierı́a Inversa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.5. Objetivos del proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2. El formato ELF 11
2.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.2. Formatos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2.1. Portable Executable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2.2. a.out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2.3. ELF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3. El formato ELF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4. La cabecera principal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5. La tabla de cabeceras de sección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.6. La tabla de cabeceras de programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6.1. Tipo de segmento pt load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.6.2. Tipo de segmento pt dynamic . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.6.3. Tipo de segmento pt interp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.7. La tabla de sı́mbolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.8. Análisis de binarios, herramientas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.8.1. Análisis estático . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

iii
iv ÍNDICE GENERAL

2.8.2. Análisis dinámico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3. Carga de programas 25
3.1. Montadores y Cargadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.2. Ejecución básica de programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.3. Carga básica en Linux de ficheros ELF . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.3.1. Carga estática . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3.2. Carga dinámica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4. Técnicas de ofuscación de código 31


4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2. Tipos de Ofuscación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2.1. Pre-compilación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2.2. Post-compilación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.3. Modificación de binarios según objetivos . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.3.1. Código malicioso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.3.2. Cifrado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

5. La utilidad ELFRecover 39
5.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2. La Herramienta ELFRecover . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.2.1. Detector de anomalı́as: ELFVerify . . . . . . . . . . . . . . . . . . . . . . . . 41
5.2.2. Modificar y revisar el binario: ELFRecover.so . . . . . . . . . . . . . . . . . . 42

6. Análisis de resultados 45
6.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.1.1. Juegos de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.2. Cifrados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.2.1. RedHat IA-32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.2.2. RedHat IA-64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.2.3. Debian y Gentoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.3. Conclusión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

7. Conclusiones y lineas abiertas de investigación 51


7.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
7.2. Objetivos conseguidos y aportaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
7.3. Lı́neas abiertas de investigación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
7.3.1. Reconstrucción de binarios a partir de procesos . . . . . . . . . . . . . . . . . 53
7.3.2. Ejecución de código de confiable . . . . . . . . . . . . . . . . . . . . . . . . . 53
ÍNDICE GENERAL v

A. Consideraciones éticas y legales 55


A.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.2. Ética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.3. Ingenierı́a Inversa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.3.1. Piraterı́a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
A.3.2. Opiniones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
A.3.3. Análisis de Sistemas Comprometidos, el peritaje . . . . . . . . . . . . . . . . 58

B. Algoritmos criptográficos 59
B.1. Algoritmos Simétricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
B.2. Hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
vi ÍNDICE GENERAL
Capı́tulo 1

Introducción

Automation is an attacker’s friend


Bruce Schneier, Secrets & Lies

¿Si un procesador puede ejecutar un programa, por qué una persona no puede ver lo que hace?
Una persona puede analizar lo que hace un programa que se ejecute en su procesador, siempre y
cuando las herramientas existentes para tal efecto se lo permitan.
En este capı́tulo se ponen en contexto las motivaciones y los objetivos que nos propusimos
asumir en la realización de este proyecto. La automatización de las técnicas de cifrado/modificación
de binarios, ası́ como el entorpecimiento sistemático que se observa en las labores de análisis de
sistemas comprometidos, nos han llevado a desarrollar una utilidad para facilitar esta tarea. Hemos
decidido utilizar la automatización en favor de los técnicos analistas.
Aportaremos una introducción a los conceptos necesarios para entender la terminologı́a utilizada
en el área de la Seguridad Informática.

1
2 CAPÍTULO 1. INTRODUCCIÓN

1.1. Máquina comprometida


Se dice que una máquina está comprometida cuando no se puede garantizar la integridad
y confidencialidad de los datos que contiene, debido a la posible manipulación de los mismos por
terceros no autorizados. Se dice que una máquina está potencialmente comprometida cuando
se han detectado indicios de actividad sospechosa en el sistema, pero sin tener una evidencia clara
de compromiso.
Entendemos por actividad sospechosa cualquier ı́ndice de actividad, ya sea en la máquina
o en la red, que no sea normal dentro del entorno en el que se encuentra instalada. En una em-
presa en la que se detecta actividad a altas horas de la madrugada habrı́a que determinar si tiene
alguna máquina programada para realizarla (mirroring, backups, etc); o si por el contrario, nadie
parece haber generado ese tráfico, en cuyo caso será considerado como un indicio. Entendemos por
evidencia de compromiso a una prueba irrefutable que nos indique que ha habido actividad no
permitida en la máquina.
Determinar si existe o no compromiso es complicado ya que depende del nivel de conocimientos
del atacante, ası́ como de su habilidad ocultando las evidencias y de lo fácil que sea en ese sistema
poder ocultarlas1 .

1.1.1. Atacantes
El compromiso de una máquina puede ser llevado a cabo por una persona con amplios cono-
cimientos en sistemas operativos y comunicaciones −no vamos a tratar aquı́ los ataques que hacen
algunos adolescentes que se bajan un programa de Internet y lo usan sin saber qué hace−.
Una máquina también puede verse comprometida por un programa (virus o gusano2 ). Hay
ciertos virus que atacan a los servicios activos de una máquina determinada, o de todas las que
encuentran con una caracterı́stica que las hace vulnerables a ese virus en concreto. En ese caso
utiliza un exploit 3 o varios de manera automática hasta que consigue infectar el servidor y desde
ahı́, buscar y atacar nuevos objetivos.

1.1.2. Motivos de ataque


Los motivos más comunes por los que una máquina puede llegar a ser comprometida:

Como puente en otras actividades. Si el objetivo del atacante está fuertemente protegido procu-
rará garantizar unos cuantos saltos antes del destino, para dificultar el seguimiento de las
conexiones en caso de un posible intento de seguimiento del ataque. En el caso de los gusanos,
usan la máquina atacada para seguir atacando otras, con lo cual el grado de ataque al cabo de
un tiempo puede llegar a ser exponencial. Algunos virus tienen como único fin extenderse al
máximo de máquinas posible y cada máquina que consiguen infectar no es más que un punto
a partir del cual seguir atacando.
1 Depende normalmente del nivel de privilegios que haya alcanzado el atacante.
2 Virusque utilizan los servicios de Internet para expandirse.
3 Programa que aprovecha una vulnerabilidad para permitir a un atacante entrar en una máquina que no le

pertenece.
1.1. MÁQUINA COMPROMETIDA 3

Figura 1.1: Estructura de un servidor en Internet, que posee un firewall−máquina que controla el tráfico entrante
y saliente de la red− y aun ası́ es vulnerable a nivel de aplicación

Obtención de datos. Una máquina que contenga datos sensibles puede ser objetivo de un ataque
para conseguir la información que posee, este es el caso por ejemplo, de las máquinas que
guardan secretos militares o industriales.

Puente para otras máquinas de la red. Si el objetivo del atacante es una red en la que una
máquina está fuertemente protegida, intentará atacar alguna otra que sea más débil, porque
es más fácil atacar una máquina desde dentro de la red, que desde fuera de ella debido a los
dispositivos de defensa perimetral o firewalls.

Conseguir aceptación dentro de un grupo. Si la red que se está atacando no tiene más interés
para el intruso que aumentar su nivel como supuesto hacker 4 , puede ser que solamente intente
aprender para conseguir cierto status dentro de un mundo en el que lo más importante son
sus conocimientos, y luego poder compartir experiencias con el resto de la comunidad.

Fines reivindicativos, polı́ticos o comerciales. Si el atacante se siente insultado por algún


grupo polı́tico o se siente indignado por una ley que le parece injusta, es posible que trate
de manifestarlo poniendo su queja en un sitio donde pueda verse, por ejemplo los ataques a
páginas web de partidos polı́ticos o de grupos con ideales de cualquier tipo.

Uso personal para el atacante. Si la máquina es cara o extraña normalmente intentará adquirir
un usuario lı́cito de la misma para poder utilizarla y aprender el máximo sobre ella. General-
mente este tipo de máquinas las utilizan para probar sus exploits o desarrollos.
4 Persona con amplios conocimientos en informática que es capaz de programar aplicaciones que exploten vulne-
rabilidades de otros programas.
4 CAPÍTULO 1. INTRODUCCIÓN

1.1.3. Modus Operandi del atacante


La forma de atacar una máquina o red varı́a dependiendo del tipo de organización que se va a
atacar. De todos modos, obedece siempre a unos patrones determinados. El tiempo estimado en el
que se realiza un ataque varı́a según el tipo de red a atacar y los objetivos, pero suele mantener una
escala acorde con los tiempos mostrados en la figura 1.2, que representa el ataque a una red con
pocas protecciones con el objetivo de utilizarla para atacar a terceros manteniendo el anonimato
del atacante.

Figura 1.2: Tiempo estimado de realización de un ataque según las etapas


1.1. MÁQUINA COMPROMETIDA 5

Los pasos que sigue un atacante cuando planea hacerse con un sitio determinado, son los
siguientes:

Recopilación de información. Se intenta averiguar toda la información posible referente a la


organización: su personal, directorios publicos de información, nombres de usuario, publi-
caciones de los trabajadores en news, páginas web públicas de la organización, contenido
indexado por los buscadores, rangos de direcciones IP que tiene la empresa, etc. A menudo se
simulan llamadas de teléfono oficiales o mailing para conseguir información de los empleados,
de secretarias, de cualquiera que pudiese contestar sus dudas por ese medio. Además inten-
tará evaluar la capacidad del equipo de técnicos ante un incidente, para valorar la viabilidad
de su ataque.

Análisis de la red. Una vez conocidos los rangos de direcciones IP que pertenecen a la empresa,
se intentará trazar un mapa de la red, mediante diferentes técnicas cuyo éxito dependerá de lo
bien configurados que estén los dispositivos de routing. Además, intentará determinar qué dis-
positivos tanto firewall como IDS −sistemas de detección de intrusiones− se encuentran en la
red, y su capacidad para detectar/parar un ataque.

Escaneos automáticos de vulnerabilidades. En esta fase se intentarán detectar vulnerabilida-


des conocidas de manera automática. También hay una parte del escaneo que se realiza man-
ualmente y pretende determinar si hay vulnerabilidades que las herramientas automáticas no
son capaces de detectar o determinar pero el atacante sı́.

Verificación manual de las vulnerabilidades. En esta etapa del ataque, se llevará a cabo el
intento de intrusión real. Se comprobará si alguna de las vulnerabilidades halladas en la etapa
anterior son útiles para materializar el ataque5 .

Escalada de privilegios. Una vez conseguido un usuario cualquiera, normalmente sin perfil de
administrador, se intentará realizar una escalada de privilegios para poder borrar las eviden-
cias que haya en la máquina y seguir utilizándola en el futuro. Además, intentará ocultar
a la vista todos aquellos programas que deje en el sistema −para garantizarse poder entrar
siempre que quiera−, y que, en caso de ser detectado, no puedan ser analizados (ver 1.5).

Expansión. A partir de ese momento intentará expandirse por la red interna, y comprometer otros
sistemas, hasta alcanzar su objetivo.

Hay otros ataques, como los que tienen lugar desde dentro de la propia organización en los
que un usuario interno parte del penúltimo punto de los comentados para conseguir permisos de
administrador.
Los virus o gusanos se expanden a través de servidores, buscan de forma automatizada IP s
con una vulnerabilidad que ellos saben explotar y la usan para comprometer esa máquina y seguir
infectando.
5 Elhecho de que exista una vulnerabilidad en un sistema no quiere decir que ésta sea explotable, ya que depende
en gran medida de la configuración del sistema
6 CAPÍTULO 1. INTRODUCCIÓN

1.1.4. Detección
Cuando la red y sus administradores son conscientes de la inseguridad que supone estar conec-
tado a internet, generalmente se instalan programas de detección de intrusiones, tanto a nivel de
red (NIDS6 ), como a nivel de máquina (HIDS7 .). En estos casos, se pretende detectar la intrusión
justo en el momento en el que se produce para evitar ası́ daños posteriores, o que el atacante tenga
la red bajo su control durante demasiado tiempo. El problema de este tipo de sistemas es que a
menudo dan falsos positivos o alarmas de supuestos ataques que no se han producido.
Según el nivel de conocimientos de los administradores y de la polı́tica de seguridad que tenga
la empresa, el tiempo que se tarda en detectar la intrusión puede ser instantáneo o durar meses.
Generalmente, si no se detecta justo en el momento en que se produce, es muy complicado hacerlo,
a no ser que el atacante sea malo.
Los archivos de registro 8 se deben revisar periódicamente. En esas revisiones es donde se detecta
la intrusión, no solamente porque aparece la conexión del atacante, ya que a veces no aparece pero
hay un salto extraño entre dos entradas, o el archivo está modificado de forma extraña, o incluso
puede haber desaparecido el archivo.
Los sistemas de detección de intrusiones comprueban periódicamente que los programas del
sistema no hayan sido modificados, inspeccionan contenido de las conexiones (algoritmos de pattern
matching 9 ), etc. Si se encuentran archivos en directorios en los que no deberı́an estar, archivos
faltantes, o hay actividad en la máquina a horas que no deberı́a haberla, se le debe notificar al
administrador debe ser avisado inmediatamente.

6 Network Intrusion Detection System.


7 Host Intrusion Detection System
8 Archivos que contienen un histórico de conexiones, también conocidos como logs.
9 Coincidencia de cadenas conocidas de diversos ataques, si el ataque es nuevo, es muy difı́cil de detectar.
1.2. HONEYPOTS 7

1.2. HoneyPots
Se llama HoneyPot o máquina señuelo a una máquina o conjunto de ellas que se pone en una
red para que parezca más fácil de atacar que el resto, o sea más llamativa que el resto. Este tipo
de máquinas se colocan en un segmento de red distinto del resto de máquinas de la organización
para evitar que faciliten el acceso al resto de la red. Suele estar mucho más vigilada aunque no lo
parezca a simple vista para detectar los ataques antes que el atacante pueda llegar a una máquina
realmente importante dentro de la red. La mayorı́a de bancos o entidades financieras tienen en sus
redes varias máquinas dedicadas a recibir ataques.

1.3. Recogida de evidencias


Una vez que la intrusión se ha detectado, comienza el proceso de recogida de pruebas, que
permite determinar las acciones de un intruso en una máquina o máquinas determinadas: la obten-
ción de evidencias que prueben que estuvo allı́ y qué hizo. Se utiliza como método de peritaje.
Normalmente a la recogida de pruebas de una máquina comprometida le sigue el de otras que
también suele haber con indicios de compromiso en el mismo segmento de red.
En Estados Unidos hay mucha más tradición que en Europa en la realizacion de este tipo de
auditorı́as. Históricamente, allı́ surgió la necesidad de realizarlas, en el entorno judicial, y por ello se
han acuñado varios términos judiciales en estos análisis. Como por ejemplo análisis forense, para
denominar a un análisis que obtiene pruebas que legalmente sirven para determinar qué sucedió con
un sistema, y de qué manera -al igual que sucede con las personas en el ámbito médico-.
Conocer el nivel de compromiso de una máquina -o incluso de una red- implica analizarla
en profundidad. Existen programas o conjuntos de herramientas llamadas Rootkits que ocultan la
entrada de un intruso en una máquina borrando automáticamente todas las entradas en archivos que
pudiesen comprometer al atacante; siempre presuponiendo que el intruso haya conseguido permisos
de administrador y los registros de ésa máquina no se envı́en a otra como medida de seguridad
añadida. Asimismo instalan puertas traseras para futuras incursiones del atacante en la máquina,
dejan sniffers funcionando para capturar tráfico y eventualmente contraseñas de otras máquinas.
Pese a no existir tradición en la realización de estos análisis, conocemos el método de realización
de los mismos, aunque nuestro sistema legal no está todavı́a suficientemente maduro para requerirlos
siempre que son necesarios. Actualmente se están llevando a cabo entre los abogados campañas de
alfabetización en estos temas.
Para realizar una auditorı́a de estas caracterı́sticas, hay que comprobar los archivos de registro,
el kernel, las aplicaciones que se ejecutan en la máquina y los binarios que haya en los distintos
directorios. A continuación pasamos a detallar cada uno de estos pasos.

1.3.1. Archivos de Registro


Se miran las conexiones que pueden resultar sospechosas en los archivos de registro, para veri-
ficar si realmente ha habido un intruso a unas horas determinadas, o simplemente era un malfun-
cionamiento de la máquina por algún otro motivo. Asimismo en los archivos de registro se pueden
8 CAPÍTULO 1. INTRODUCCIÓN

descubrir ataques de denegación de servicio en los que no ha habido intrusión propiamente dicha.
(Un ataque de denegación de servicio es un intento de saturar una máquina, ya sea su ancho de
banda o su capacidad de cálculo, desde fuera, mediante conexiones desde muchas IPs, pings, etc.)

1.3.2. Kernel y módulos


Sobre todo cuando se habla de máquinas Linux, hay que tener en cuenta el núcleo de la misma.
El kernel es un binario casi igual que el resto de los que se encuentran en la máquina -aunque sin
cabecera-, por lo tanto también susceptible de haber sido modificado, recompilado, o tener cargados
módulos que actúen al servicio del atacante, por ello, generalmente para realizar un análisis de este
tipo se carga el sistema con un núcleo que se puede garantizar que está limpio, es tı́pica la situación
de arrancar desde un diskette o desde un CDROM.

1.3.3. Aplicaciones en ejecución


Las aplicaciones que se están ejecutando en el momento de apagar la máquina para su análisis
son muy importantes, de cara a conocer si hay un sniffer activo, o algún otro tipo de programa que
pudiera ser una puerta trasera. Si se puede realizar un volcado limpio de la memoria será muy útil
a la hora de realizar el análisis posterior offline.

1.3.4. Binarios existentes


Lo primero que se comprueba para determinar si el usuario ha dejado una puerta trasera en el
sistema, son los suid’s, o ejecutables que permiten ejecutar una aplicación con unos privilegios que
no son los del ejecutable original.
Los tipos de binarios que podemos encontrar modificados por el atacante en un sistema, son
tanto los ejecutables como las librerı́as compartidas del sistema. En muchos casos, estos binarios han
sido cifrados utilizando técnicas de ofuscación (ver capı́tulo 4), para hacer que sean prácticamente
imposibles de depurar y evitar ası́ poder determinar qué hacen.

1.4. Ingenierı́a Inversa


En una sociedad que depende cada dı́a más de procesos automáticos, la forma de hacer las
cosas es cada vez más importante. Muchas empresas desarrolladoras de software, intentan proteger
sus códigos mediante números de serie que sólo serán utilizados por clientes lı́citos, o proteger los
CD’s con sistemas anti-copia. Este software suele ser excesivamente caro, hay quien se dedica a
analizar los binarios de los sistemas y ver de qué forma se pueden evitar los sistemas de protección
anti-copia, para evitar el pago, a este proceso se le llama ingenierı́a inversa.
Se denomina también ingenierı́a inversa al proceso de determinar cómo funciona un algoritmo
o código fuente de un programa para poder hacer alguna modificación. Por lo tanto el término no
tiene −como a menudo se tiende a pensar− connotaciones negativas, siempre y cuando el fin para
el que se utiliza sea lı́cito.
Últimamente han sido creados sistemas de anti-debug y anti-análisis, que se basan en confundir
1.5. OBJETIVOS DEL PROYECTO 9

al programa que depura el código, y se pueden utilizar para proteger todo tipo de software, incluido
el considerado como malicioso. Existen herramientas en el mercado capaces de realizar este tipo
de análisis siempre y cuando no se haya utilizado una técnica de cifrado avanzada −como veremos
más adelante en el capı́tulo 4−.

1.5. Objetivos del proyecto


Los objetivos del proyecto van encaminados a dar respuesta a la pregunta que comentamos en
la introducción:
−¿Si un binario puede ejecutarse en un procesador, de qué manera podemos ver lo que hace?
Además queremos ver la forma de podrı́a automatizar el análisis de este tipo de aplicaciones de
manera que no resulten tan difı́ciles de tratar por los analistas de sistemas comprometidos.
En concreto los objetivos son:

Determinar por qué algunas aplicaciones son difı́ciles de analizar por los depuradores exis-
tentes. Apuntar cuáles son las causas y consecuencias de ello.

Dejar claramente documentado que la ofuscación de binarios existe y qué significa que alguien
se encuentre un binario de estas caracterı́sticas en su máquina.

Desmitificar cierto tipo de código malicioso. Aportar documentación suficiente sobre el tema
para que cualquier técnico que se encuentre ante un programa de estas caracterı́sticas sea
capaz de abordar el problema de forma eficaz y coherente.
10 CAPÍTULO 1. INTRODUCCIÓN

Construir algún tipo de herramienta, en este caso ELFRecover, que consiga dar un paso para
conseguir de poder analizar cualquier tipo de binario que se quiera ejecutar en una máquina.

Este estudio se realizará para dos entornos, IA-32 e IA-64, teniendo presente que el código ma-
licioso existente para entornos basados en tecnologı́a Itanium c , por el momento, es prácticamente

inexistente.
Capı́tulo 2

El formato ELF

ELF is a binary format designed to support


dynamic objects and shared libraries
NetBSD Documentation

Conocer la tecnologı́a, y el tipo de binarios con los que vamos a trabajar, garantiza que podamos
darles el tratamiento adecuado a la hora de conseguir alcanzar los objetivos planteados. En este
capı́tulo analizaremos el formato ELF, centrándonos en la cabecera, que será la información clave
a la hora de descifrar un binario modificado, como veremos.
Además se dará un repaso a las herramientas de análisis de este tipo de formato de binarios que
utiliza Linux.

11
12 CAPÍTULO 2. EL FORMATO ELF

2.1. Introducción

Un archivo en formato binario que puede ser ejecutado en una plataforma o sistema operativo,
es un archivo que cumple con una serie de especificaciones creadas por motivos de compatibilidad
y estandarización. Existen dos tipos de documentos fundamentales que definen los requerimien-
tos: por un lado la especificación propia del formato −qué forma tiene, cómo almacena los datos,
qué cabecera hay primero y cuál después, etc−, y por otro lado la ABI 1 , especifica qué debe cumplir
un sistema operativo para ejecutar un tipo de formato binario en una arquitectura hardware deter-
minada.
Generalmente los archivos ejecutables tienen una parte de código y una de datos, donde se
almacenan las cabeceras (que contienen información sobre el binario), las diferencias entre las partes
de un binario almacenado en disco y de uno en memoria se pueden observar en la figura 2.1.

Figura 2.1: Binario ejecutable en disco / en memoria

La carga del programa en memoria se realiza bajo demanda (el usuario invoca el programa en
lı́nea de comandos) y de manera transparente: el sistema operativo lee el fichero ejecutable −es
imprescindible que sea de un formato que entienda−, y lo pone en memoria copiando el código, los
datos, y reservando espacio para la pila y los datos no inicializados. El ejecutable siempre ocupa
mas en memoria que en disco. Después se cede el control al programa propiamente. De este proceso
hablaremos en profundidad en el apartado 3.2.
El espacio de direcciones del proceso es el conjunto de direcciones que ocupa un proceso
en ejecución, a las que solamente él tiene acceso, es el espacio de memoria al que se hará referencia
a lo largo de este trabajo.

1 Application Binary Interface: http://www.linuxbase.org/spec/refspecs/.


2.2. FORMATOS 13

2.2. Formatos
Un formato binario no es más que una estructura de datos con la cual el sistema operativo es
capaz de crear un proceso que ejecute cierta tarea −la tarea a realizar vendrá determinada por el
contenido del binario−.
Los programadores escriben código fuente en cualquier lenguaje, y éste es traducido por el
compilador en un fichero objeto. Cada objeto contiene variables globales, inicializadas o no, datos
constantes, recursos, código máquina, nombres simbólicos para el montaje, e información de depu-
ración. Después se monta el binario ejecutable para poder realizar posteriormente su ejecución tal
y como muestran las figuras 3.4 y 3.5 para los binarios estáticos y dinámicos respectivamente.
A continuación resumiremos a grandes rasgos los formatos binarios más representativos a la
hora de entender la ejecución de procesos.

2.2.1. Portable Executable


El formato de binario Portable Executable, más conocido como PE, es utilizado por las platafor-
mas Win32.
La arquitectura Win32 está basada en un sistema de librerı́as compartidas. El sistema operativo
también las utiliza constantemente y por ello, es necesario poder compartir código por los distintos
programas.

Figura 2.2: Formato de binario PE almacenado en disco


14 CAPÍTULO 2. EL FORMATO ELF

Por motivos de compatibilidad con sistemas anteriores, los binarios PE tienen una cabecera
de tipo DOS .EXE 2 (IMAGE DOS HEADER), a continuación de esta cabecera, se encuentra un
pequeño programa DOS, cuyo código es llamado en caso de que haya alguna interrupción del progra-
ma para escribir el error correspondiente. A continuación se encuentra el archivo PE propiamente
dicho, cuya estructura se detalla en la figura 2.2.
No entraremos a detallar este formato, porque el proceso de enlazado dinámico que realmente
nos interesa es el del formato ELF, que veremos más adelante.

2.2.2. a.out
Este formato es el predecesor de ELF en sistemas Unix, ya que AT&T creyó necesario un formato
que diera mejor soporte para la compilación cruzada y la depuración de procesos.

Figura 2.3: Proceso a.out

En el caso más sencillo un a.out contiene una pequeña cabecera seguida de codigo ejecutable y
los valores iniciales para la sección de datos (Ver figura 2.3).
Para cargar un archivo a.out, el sistema lee en la información de la cabecera los tamaños de
los segmentos. Después busca si hay código de librerı́as compartidas que necesite para hacerlas
accesibles a su espacio de direcciones. Crea varios segmentos privados de datos, para la pila y el
heap, actualiza los registros con los valores adecuados y cede la ejecución al punto de entrada del
programa.

2.2.3. ELF
ELF nace en la familia System V de Unix y como consecuencia ha sido adoptado por los sistemas
operativos Linux, en sus variantes de 32 y de 64 bits. Es versátil y se adapta perfectamente a las
necesidades de la comunidad Open Source, ası́ como a la de muchos sistemas propietarios.
2 Formato binario anterior a PE.
2.2. FORMATOS 15

Figura 2.4: Formato de binario ELF

En la figura 2.4 se puede observar que el formato ELF tiene dos vistas, una vista de secciones
que se utiliza desde el punto de vista de la depuración de procesos, y la vista de segmentos, que
es la utilizada en la ejecución propiamente dicha. La vista de depuración es totalmente prescindible
a la hora de ejecutar el programa.
Los detalles sobre este formato los explicaremos en profundidad a continuación porque es el
formato sobre el que trabajaremos.
16 CAPÍTULO 2. EL FORMATO ELF

2.3. El formato ELF


Como se especifica en la documentación del Tools Interface Standard Committee 3 , hay tres
tipos principales de binarios ELF:

Un archivo reubicable, es aquel que contiene código y datos para ser montados y crear un
ejecutable o un objeto compartido.

Un ejecutable contiene un programa preparado para su ejecución.

Un objeto compartido contiene código y datos listos para ser enlazados en dos contextos
diferenciados según el momento en el que tienen lugar:

• En tiempo de compilación. El enlazador puede procesarlo con otros objetos compartidos


y reubicables para crear otro objeto.
• En tiempo de ejecución. El enlazador dinámico o el cargador - que es el módulo que
se encarga de crear en memoria el mapa del proceso como veremos más adelante-, lo
combina con un fichero ejecutable y otros archivos compartidos para crear la imagen del
proceso.

Este formato es ampliamente utilizado en entornos Linux, y por ello es de los más conocidos,
junto con a.out. Se encuentran en funcionamiento en formato de 32 bits y en formato de 64.

2.4. La cabecera principal


La cabecera ELF tiene principalmente información sobre cómo acceder a la tabla de cabeceras de
sección y a la tabla de cabeceras del programa. Esto es porque todo archivo ELF, como vimos en el
capitulo anterior, puede verse desde dos puntos de vista: desde el punto de vista del enlazado −como
una lista de secciones− o desde el punto de vista de ejecución −como una lista de segmentos−. La
tabla de cabeceras de sección contiene información sobre cómo acceder a los datos de reubicación y
a los de sı́mbolos, mientas que la tabla de cabeceras de programa contiene información sobre cómo
crear la imagen de proceso del programa.
La cabecera ELF tiene los campos que explicamos a continuación, (ver 2.5).

e ident: ELF provee un entorno de ficheros objeto que soporta multiples procesadores, codifica-
ciones de datos y arquitecturas. Este byte especifica cmo interpretar el fichero, qué versión
de cabeceras usa, que codificación de datos y para qué clase de arquitectura está compilado
(32-bits o 64-bits). Los 4 primeros bytes de la cabecera son 0x7f, ’E’, ’L’ y ’F’.

e type: Este campo identifica qué tipo de ELF es, tal y como hemos comentado anteriormente
puede ser reubicable, ejecutable, u objeto compartido.

e machine: Este valor especifica la arquitectura requerida: AT&T, SPARC, Intel 80386, Intel IA-
64, Motorola 6800, Motorola 8800, Intel 8086 o MIPS RS3000.
3 TIS, (ver las especificaciones completas en [21]).
2.5. LA TABLA DE CABECERAS DE SECCIÓN 17

typedef struct {
unsigned char e ident[EI NIDENT];
Elf Half e type;
Elf Half e machine;
Elf Word e version;
Elf Addr e entry;
Elf Off e phoff;
Elf Off e shoff;
Elf Word e flags;
Elf Half e ehsize;
Elf Half e phentsize;
Elf Half e phnum;
Elf Half e shentsize;
Elf Half e shnum;
Elf Half e shstrndx;
} Elf Ehdr

Figura 2.5: Cabecera ELF

e version: Identifica la versión de fichero.

e entry: Dirección virtual a la cual el intérprete transfiere el control después de cargar el objeto
de manera satisfactoria.

e phoff: Desplazamiento en el fichero de la tabla de cabeceras del programa. Esta tabla contiene
información sobre los diferentes segmentos que tendrá el proceso.

e shoff: Desplazamiento en el fichero de la tabla de cabeceras de secciones. Esta tabla enumera las
diferentes secciones que contiene el archivo ELF.

e phentsize: Este campo contiene los flags especı́ficos asociados al procesador.

e phnum: Número de entradas de la tabla de cabeceras del programa.

e shentsize: El tamaño de de cada entra de la cabecera de secciones del archivo.

e shnum: Número de entradas de la cabecera de secciones.

e shstrndx: Todas las cadenas contenidas de esta cabecera estan representadas por punteros a
una tabla de cadenas. Este campo mantiene información del ı́ndice de la tabla de cabeceras
de sección.

2.5. La tabla de cabeceras de sección


Las secciones de un objeto ELF están descritas en la tabla de cabeceras de sección. Esta tabla
empieza en el byte e shoff (que indica un desplazamiento desde el principio del archivo), y contiene
e shnum entradas de tipo Elf Shdr (ver figura 2.7).
18 CAPÍTULO 2. EL FORMATO ELF

Figura 2.6: Vista de depuración y vista de ejecución

sh name: El nombre de la seción. Como cualquier cadena, es un ı́ndice a una cadena de la tabla
de entradas.

sh type: Describe qué clase de información contiene esta sección (información de sı́mbolos, de
reubicación, una tabla de hash, etc). Nótese que si el tipo de SHT NOBITS, no existe la sección
cuerpo en el archivo, solo la cabecera (esto sucede, por ejemplo, con la sección de datos no
inicializados .bss).

sh flags: Varios atributos.

sh addr: si esta sección va a ser mapeada en memoria, esta es la dirección donde residirá el primer
byte.

sh offset: Este atributo contiene el desplazamiento del archivo donde el cuerpo de la sección em-
pieza (cero si es de tipo SHT NOBITS )

sh size: El tamaño de la sección (cero si es de tipo SHT NOBITS ).

sh link: Un enlace al ı́ndice de la tabla de cabeceras de secciones. Su significado depende de


sh type. En todas aquellas secciones que contienen información sobre los sı́mbolos (SHT DYNSYM,
SHT SYMTAB), este atributo contiene el ı́ndice de la cabecera de la sección de la tabla de
cadenas usada en esta sección.

sh info: Información extra, cuya interpretación depende del tipo de la sección.


2.6. LA TABLA DE CABECERAS DE PROGRAMA 19

typedef struct {
Elf Word sh ename;
Elf Word sh type;
Elf Word sh flags;
Elf Addr sh addr;
Elf Off sh offset;
Elf Word sh size;
Elf Word sh link;
Elf Word sh info;
Elf Word sh addralign;
Elf Word sh entsize;
} Elf Shdr

Figura 2.7: Cabecera de secciones ELF

sh addralign: Restricciones de alineamiento.

sh entsize: En la mayoria de las secciones consiste en una tabla con un número de entradas de
tamaño fijo, por ejemplo, la tabla de simbolos o la de reubicación.

Para más información mirar [21].

2.6. La tabla de cabeceras de programa


La imagen de un proceso tiene segmentos para gestionar su texto, datos, pila y demás. Las
entradas de la tabla de cabeceras del programa describe esos segmentos y cómo construir la imagen
de un proceso. Las secciones que hemos visto antes estn contenidas en uno o más segmentos.
Esta tabla empieza en el byte e phoff desde el principio del fichero, y contiene e phnum entradas
como las descritas en la siguiente figura (2.8).

typedef struct {
Elf Word p type;
Elf Off p offset;
Elf Addr p vaddr;
Elf Addr p paddr;
Elf Word p filesz;
Elf Word p memsz;
Elf Word p flags;
Elf Word p align;
} Elf Phdr

Figura 2.8: Cabecera de programa ELF

p type: La clase de segmento.

p offset: Es el desplazamiento en bytes desde el inicio del fichero, en el cual el primer byte del
segmento reside.
20 CAPÍTULO 2. EL FORMATO ELF

Nombre Tipo Atributos


.bss SHT NOBITS SHF ALLIC + SHF WRITE
.comment SHT PROGBITS none
.data SHT PROGBITS SHF ALLOC + SHF WRITE
.data1 SHT PROGBITS SHF ALLOC + SHF WRITE
.debug SHT PROGBITS none
.dynamic SHT DYNAMIC variable
.dynstr SHT STRTAB SHF ALLOC
.dynsym SHT DYNSYM SHF ALLOC
.fini SHT PROGBITS SHF ALLOC + SHF EXECINSTR
.got SHT PROGBITS variable
.hash SHT HASH SHF ALLOC
.init SHT PROGBITS SHF ALLOC + SHF EXECINSTR
.interp SHT PROGBITS variable
.line SHT PROGBITS none
.note SHT NOTE none
.relname SHT REL variable
.relaname SHT RELA variable
.rodata SHT PROGBITS SHF ALLOC
.rodata1 SHT PROGBITS SHF ALLOC
.shstrtab SHT STRTAB none
.strtab SHT STRTAB variable
.symtab SHT SYMTAB variable
.text SHT PROGBITS SHF ALLOC + SHF EXECINSTR

Cuadro 2.1: Lista de secciones en un objeto ELF ejecutable de Linux

p vaddr: Dirección virtual donde este segmento debe ser mapeado. Si este segmento corresponde
a un ejecutable ELF, el valor de ser obedecido por el intérprete, ya que los objetos ejecuta-
bles utilizan referencias a direcciones virtuales absolutas. Para las librerı́as compartidas, el
intérprete puede usar o no este valor cuando crea el proceso imagen (las librerı́as compartidas
usan código independiente de la posición de memoria en que se encuentra).

p paddr: Dirección fı́sica donde el segmento reside. Este atributo es ignorado por los sistemas
donde el direccionamiento fı́sico no está permitido, como System V o BSD.

p filesz: Contiene el tamaño en bytes de ese segmento en el archivo.

p memsz: Contiene el tamaño en bytes en memoria.

p align: Este atributo proporciona el valor al cual los segmentos están alineados en memoria y en
el archivo.

Hay varios tipos de segmentos, como se explicita en el siguiente cuadro (2.2).

2.6.1. Tipo de segmento pt load


Los segmentos de este tipo serán mapeados en memoria durante la ejecución. Se leerán p memsz
bytes del archivo. Si p memsz es mayor que p filez el espacio extra de memoria será rellenado con
ceros. Los segmentos que pueden ser cargados aparecen en orden ascendente (por el valor p vaddr ).
2.6. LA TABLA DE CABECERAS DE PROGRAMA 21

Nombre Valor
PT NULL 0
PT LOAD 1
PT DYNAMIC 2
PT INTERP 3
PT NOTE 4
PT SHLIB 5
PT PHDR 6

Cuadro 2.2: Lista de tipos de segmentos disponibles

Normalmente hay dos segmentos de este tipo, uno contiene todas las secciones de solo lectura.
Las secciones listadas en el cuadro 2.1 están distribuidas en los dos segmentos que muestra el cuadro
2.3.

Lectura-Ejecución
.interp
.note
.hash
.dynsym Lectura-Escritura
.dynstr .data
.gnu.version .eh frame
.gnu.version r .dynamic
.rel.dyn .ctors
.rel.plt .dtors
.init .got .bss
.plt
.text
.fini
.rodata

Cuadro 2.3: Lista de secciones cargadas en memoria en el segmento de texto (solo lectura) o en el de datos
(lectura-escritura)

Nótese que los datos no inicializados no utilizan espacio de disco, y por lo tanto p memsz es
normalmente mayor que p filesz. Cuando se crea la imagen de un proceso, el sistema expande el
segmento hasta que ocupa p memsz bytes. La expansión se hace al final, y contendrá los datos no
inicializados, por eso la sección .bss viene la última.
El intérprete puede mapear objetos compartidos ELF en cualquier sitio, pero debe respetar el
orden de las secciones, ya que las referencias entre secciones son relativas y pueden existir.

2.6.2. Tipo de segmento pt dynamic

Este segmento contiene la sección .dynamic, un vector de elementos del tipo descrito en la figura
2.9.
Cada una de estas entradas nos da información sobre el enlazado dinámico del objeto ELF. De-
pendiendo del valor de d tag (la clase de información proporcionada) tendremos un valor numérico
o un puntero como parámetro.
22 CAPÍTULO 2. EL FORMATO ELF

typedef struct {
Elf Sword d tag;
union {
Elf Word d val;
Elf Addr d ptr;
} d un;
} Elf Dyn;

Figura 2.9: Entrada del segmento dinámico

2.6.3. Tipo de segmento pt interp


Los objetos ELF ejecutables dinámicos requieren de un programa que los cargue y resuelva las
dependencias que sean necesarias, el intérprete. Este intérprete lee el fichero ELF, crea la imagen
del proceso apropiada, y transfiere el control al punto de entrada. El intérprete por defecto en Linux
es ld-linux.so.2.

2.7. La tabla de sı́mbolos


La información de los sı́mbolos está contenida en dos secciones: .symtab y .dynsym.
La sección .dynsym contiene información sobre sı́mbolos externos usados, y los sı́mbolos dinámi-
cos exportados (en el caso de ser una librerı́a compartida). Esta información es la parte esencial,
un subconjunto de la información presente en .symtab, y no puede ser borrada.
Las dos secciones contienen una tabla con un número de entradas de tamaño fijo.

La sección .symtab

Esta sección debe contener cualquier clase de información sobre sı́mbolos: Las funciones locales y
variables, información sobre los ficheros de código fuente originales (para propósitos de depuración),
sı́mbolos dinámicos usados, etc...
A diferencia de la sección .dynsym, aquı́ encontramos infomación sobre sı́mbolos usados pero
no exportados a otros módulos. Estos sı́mbolos no están presentes en la tabla .dynsym porque no
son necesarioes para el proceso de enlazado dinámico. Los sı́mbolos no exportados no pueden ser
usados desde fuera, por tanto las llamadas internas pueden ser realizadas referenciando al contador
de programa y no se necesita reubicación.

La sección .dynsym

Esta sección contiene información sobre sı́mbolos exportados por este módulo, y sı́mbolos im-
portados de otros módulos. Ambas clases de información son necesarias para el cargador dinámico,
cuando resuelve las referencias en tiempo de ejecución. Esta información puede ser destruida de la
sección .symtab, pero no puede ser borrada de la sección .dynsym.
2.8. ANÁLISIS DE BINARIOS, HERRAMIENTAS 23

2.8. Análisis de binarios, herramientas


En los sistemas operativos actuales, existe cada vez en mayor medida una preocupación intrı́nseca
por la seguridad de las aplicaciones que se ejecutan, y por ofrecer al administrador y al usuario
una gama más colorida de herramientas que les puedan ayudar a estudiar un determinado código
binario, presentarlos en un formato que pueda ser leı́do y estudiado.
Dado que estas utilidades están ampliamente extendidas, la mayor parte del código malicioso
que existe, y más concretamente el que ataca sistemas Linux, intenta evitar las técnicas que utilizan
estas aplicaciones, para no ser analizado por los expertos.
En nuestra opinión, cualquier código que puede ser ejecutado en un procesador, deberı́a poder
ser analizado para saber si realiza funciones inesperadas, por lo tanto intentaremos realizar esta
labor de análisis de forma que no sea posible evadirla, al menos no con técnicas conocidas.
En el momento que se consiga lo expuesto anteriormente, será mucho más fácil analizar si un
código realiza una actividad maliciosa o no, y determinar si un archivo tiene determinado virus,
evitando ası́ falsos positivos (o detecciones de virus incorrectas).

2.8.1. Análisis estático


El análisis estático consiste en extraer información de las cabeceras del binario, para poder
averiguar el máximo posible sin necesidad de ejecutar el fichero. Las herramientas que Linux pro-
porciona de estas caracterı́sticas utilizan la librerı́a BFD4 . Esto hace que una vulnerabilidad o fallo
de diseño en la librerı́a, facilite a los programas que ofuscan binarios la labor de confundir el análisis.

readelf

readelf muestra toda la información que se puede extraer estáticamente de los campos de
información y control de los binarios ejecutables ELF.
readelf extrae toda la información posible del archivo de manera estática, sin necesidad de
ejecutarlo, y por lo tanto, no es eludible.

objdump

objdump muestra información sobre archivos objeto. Está pensada como herramienta de soporte
a los programadores, por lo tanto la vista de secciones es muy importante. La forma más sencilla de
eludir esta herramienta es modificar la cabecera principal del fichero ELF para que objdump crea
que no existe información de depuración.

2.8.2. Análisis dinámico


El análisis en tiempo de ejecución de un binario nos muestra qué llamadas a sistema se realizan,
en qué momento, qué librerı́as se llaman, contenido de los registros, direcciones accedidas, ...
4 Binary File Descriptor Library.
24 CAPÍTULO 2. EL FORMATO ELF

gdb

gdb es el depurador por excelencia en Linux. Está basado en BFD al igual que las herramientas
anteriormente citadas, sobre todo para desensamblar partes de código.
gdb no solamente ejecuta el programa, sino que permite poner puntos de parada (breakpoints),
revisar el estado de los registros en un momento determinado, etc. Dado que es una herramienta
compleja, a menudo las técnicas de ofuscación se limitan a provocar un acceso a memoria indebido,
o a enviarle instrucciones int3, para que piense que tiene breakpoints donde realmente no los hay.
Existen otras dos herramientas que realizan un subconjunto de las operaciones realizadas por
gdb, que son:

strace: Es un programa que muestra, en tiempo de ejecución, las llamadas al sistema y signals que
tengan lugar a partir de una ejecución de un binario.

ltrace: Es un programa que traza, en tiempo de ejecución, las llamadas al librerı́as dinámicas. Se
puede trazar la ejecución con más o menos nivel de profundidad en función de los parámetros
que se usen.

gtrace: Genera un informe sobre las funciones accedidas en función de la información extraı́da de
una ejecución determinada.
Capı́tulo 3

Carga de programas

Binary format: A format for representing data used for some applications
webopedia.com

Los ficheros que cargan datos en formato binario, además de utilizarse como soporte para al-
gunas aplicaciones, se utilizan por los sistemas operativos para conocer lo que hace un programa
determinado y poder ejecutarlo.
En este capı́tulo explicamos los requerimientos para la carga de programas que pueden ser
ejecutados por un sistema operativo, ası́ como de sus distintas caracterı́sticas.

25
26 CAPÍTULO 3. CARGA DE PROGRAMAS

3.1. Montadores y Cargadores


El montador (linker ) y el cargador (loader ) realizan tareas complementarias pero muy parecidas,
conviene tener claro qué hace cada uno y en qué momento para poder comprender la carga de
procesos. Dependiendo del sistema y de la versión, hay tareas que puede realizar indistintamente
uno del otro.

Carga de Programa. Es el procedimiento mediante el cual el cargador realiza la copia de un


programa de disco a memoria principal para que pueda ser ejecutado.

Reubicación. Los compiladores y ensambladores generalmente crean cada fichero con las direc-
ciones de programa empezando en 0x0, pero pocas máquinas permiten cargar un programa
en la posición 0x0. Si un programa necesita de otros para ser ejecutado, o crea nuevos subpro-
gramas, todos deben cargarse en direcciones no coincidentes. La reubicación es la acción de
cargar un programa y reubicar sus direcciones con tal de cargarlo en una dirección adecuada.
Esta acción la realiza el montador.

Resolución de sı́mbolos. Cuando un programa se construye a partir de otros, el montador re-


suelve el sı́mbolo y genera una tabla con información que se incluye en el binario para poder
realizar esta resolución en tiempo de ejecución.

Figura 3.1: Entradas y salidas del montaje de un programa

El montaje, tal y como se puede observar en la figura 3.1, es un proceso que a partir de una
serie de archivos, da como resultado el ejecutable y toda la información necesaria para poder ser
ejecutado.
3.2. EJECUCIÓN BÁSICA DE PROGRAMAS 27

Figura 3.2: Entradas y salidas de la carga de un programa

Todos los sistemas operativos tienen una o varias Application Binary Interface 1 , que son una
serie de requerimientos que debe cumplir cualquier binario que se quiera ejecutar en esa plataforma.
Asimismo los programadores de compiladores deben tenerlo en cuenta a la hora de generar los
programas que serán ejecutados.

3.2. Ejecución básica de programas


Un sistema operativo puede ejecutar varios tipos de binarios distintos, en este trabajo estudi-
aremos el formato ELF en profundidad, que hemos descrito en el capı́tulo 2.
Para poder ejecutar un programa, éste tiene que estar cargado en memoria. La forma en que el
sistema operativo carga el binario, nos da información sobre la ejecución de procesos y junto con la
ABI del fabricante podremos determinar los requerimientos para la carga de un programa.
Podemos decir, a grandes rasgos, que el sistema operativo debe realizar las siguientes tareas
antes de poder ejecutar un programa concreto.

1. Leer de la cabecera del fichero objeto la información para calcular el espacio que se necesita
en memoria.

2. Mapear en memoria los datos y código que sea necesario del archivo binario. Este proceso
dependerá del formato del binario y del sistema operativo.
1 Generalmente denominada ABI. Véase a modo de ejemplo la de Linux:
http://www.linuxbase.org/spec/refspecs/.
28 CAPÍTULO 3. CARGA DE PROGRAMAS

Figura 3.3: Carga de un binario en Linux

3. Inicializar a cero las partes del código de datos no inicializados, si la memoria virtual del
sistema no lo hace automáticamente.

4. Crear un segmento de pila.

5. Inicializar las variables que tiene valores inicialmente, ası́ como los argumentos de programa
y las variables de entorno.

6. Actualizar el Instruction Pointer con el entrypoint (punto de entrada al programa).

3.3. Carga básica en Linux de ficheros ELF


Cuando hablamos de un sistema concreto y de un tipo de binarios concreto, hay ciertas parti-
cularidades a tener en cuenta. En nuestro caso vamos a hablar de Linux, con kernel 2.4, y ficheros
ELF. Para situarnos realmente en el contexto del sistema operativo Linux, veremos lo que pasa
antes de lo descrito en el apartado anterior.
Antes de ceder el control al punto de entrada al programa, el sistema realiza las siguientes
llamadas.

1. El intérprete de comandos crea un nuevo proceso (fork) y ejecuta el programa con una
llamada a la función execve, que forma parte de glibc.

2. glibc hace una llamada al sistema syscall execve −puede encontrarse el código fuente de
la misma en uno de los archivos llamados execve.c dentro de los fuentes de la librerı́a−.
3.3. CARGA BÁSICA EN LINUX DE FICHEROS ELF 29

3. La llamada al sistema sys execve, acaba llamando a do execve, que está en


fs/exec.c.

A continuación, cuando cede el control al punto de entrada, ya se han realizado la carga de


las librerı́as necesarias, la comprobación de que el binario realmente cumple con la ABI, y se han
actualizado los registros a los valores necesarios.

3.3.1. Carga estática


La carga de un binario estático en memoria es mucho más simple que la de los enlazados
dinámicamente, ya que no requiere de la carga de librerı́as compartidas. En este caso el kernel
mapea el binario en memoria y cede la ejecución al entrypoint, o punto de entrada al programa,
para que se ejecute.

Figura 3.4: Compilación y montaje estático

Todos los sı́mbolos y llamadas a las distintas librerı́as compartidas han sido resueltas en tiempo
de compilación y en tiempo de ejecución no es necesario tener accesible el código, ya que ha sido
incluido en el ejecutable.

3.3.2. Carga dinámica


El cargador dinámico es el responsable de cargar el archivo ejecutable y las librerias compartidas
que necesita, y crear el proceso imagen. El cargador dinámico forma parte de la librerı́a base del
sistema (glibc).
El comportamiento del cargador dinámico puede ser modificado por algunas variables de entorno.
Estas variables están accesibles desde los programas y pueden ser ligeramente diferentes en cada
30 CAPÍTULO 3. CARGA DE PROGRAMAS

Figura 3.5: Compilación y montaje dinámico

implementación. En concreto, tendremos en cuenta dos de ellas:

LD PRELOAD: Esta variable de entorno indica una librerı́a que será cargada junto al proceso
en memoria, y generalmente se utiliza para programar una acción que se realizará antes del
programa. Si el programa es suid root, esta variable no será utilizada a no ser que el uid real
del propietario del programa sea 0.

: Esta variable contiene en Linux el nombre del último comando ejecutado en una shell determi-
nada.
Capı́tulo 4

Técnicas de ofuscación de código

One Ring to rule them all,


One Ring to find them,
One Ring to bring them all
and in the darkness bind them
J. R. R. Tolkien, The Lord of the Rings

Las técnicas de ofuscación de código consiguen confundir a las distintas herramientas de análisis
de código binario, para que no puedan ser depurados ciertos binarios. Esto puede resultar interesante
para dos tipos principales de códigos: el malicioso para no dejar entrever ası́ qué realiza, y el
propietario que se intenta proteger con técnicas anti-copia y en el que basan sus licencias algunos
productos de renombre.

31
32 CAPÍTULO 4. TÉCNICAS DE OFUSCACIÓN DE CÓDIGO

4.1. Introducción
La criptografı́a, en el contexto de la Informática, es la ciencia que estudia mediante algoritmos
matemáticos los métodos y procedimientos para codificar la información de tal manera que se
mantenga la confidencialidad −que tengan la clave adecuada puedan tener acceso a la versión
original de los mismos−, y la integridad −asegurar que estos datos no fueron modificados entre el
remitente y el destinatario−. Los distintos algoritmos basan su seguridad, en su gran mayorı́a, en
problemas irresolubles en tiempo polinómico. En concreto se basan en unos problemas de tipo NP o
NP-completos que tienen como caracterı́stica principal que se resuelven en tiempo exponencial pero
su solución puede verificarse en tiempo polinómico −para información más detallada ver [14]−. En
criptografı́a significa que conociendo la clave se puede verificar que es cierta en tiempo polinómico
pero sin conocerla se tardarı́a demasiado tiempo en encontrarla1 .
El criptoanálisis2 es el campo de la matemática que se encarga de analizar los algoritmos
criptográficos y determinar si tienen algún fallo que permita descifrar los datos protegidos en tiempo
menor que la fuerza bruta.

Figura 4.1: Algoritmos criptográficos

1 Donde demasiado toma como punto de referencia la capacidad de cálculo de las máquinas actuales. A esta técnica
se la conoce como fuerza bruta, porque busca la solución probando todas las soluciones posibles
2 Según la Real Academia de la Lengua Española es el arte de descifrar criptogramas.
4.2. TIPOS DE OFUSCACIÓN 33

En castellano existen varias palabras referentes a códigos y textos modificados que tenemos en
cuenta para no cometer abusos de lenguaje3 :

Codificar es transformar mediante reglas de un código la formulación de un mensaje.

Cifrar es transcribir en guarismos, letras o sı́mbolos un mensaje cuyo contenido se quiere ocultar.

Ofuscar es deslumbrar, turbar a la vista.

En este trabajo hablaremos de cifrado entendiéndolo como un proceso reversible, por lo tanto
existe un tipo de codificación inherente a él.
Y entenderemos por ofuscación al proceso de cifrar un binario de manera que pueda ser ejecutado
pero no depurado.

4.2. Tipos de Ofuscación


Hay dos grupos de técnicas fundamentales de ofuscación: aquellas que tienen lugar antes de la
compilación del programa, y por lo tanto hay que tener el código fuente para poder aplicarlas; y
aquellas que tienen lugar a partir de un binario, o tienen lugar después de la compilación.

4.2.1. Pre-compilación
En este caso el programa intenta detectar desde su propio código, si está siendo trazado por un
depurador, o se encuentra en un entorno de ejecución usual.
A continuación mencionamos algunas técnicas que se basan en ofuscar la depuración antes de
la compilación, que han sido documentadas por Iñaki López4 .

Descriptores de archivos. Sabiendo el número de descriptor de archivo primero que se asigna


en una ejecución, podemos deducir que está ejecutándose un programa solo, o con el gdb.

Variables de entorno. Cuando se ejecuta un programa, las variables de entorno pueden variar.
Por ejemplo en linux, [ ] tiene el nombre del proceso que se está ejecutando, ası́ podrı́amos
detectar a strace, ltrace (cuyo nombre sale en la variable de entorno) e incluso a gdb, que
no modifica esa variable. La técnica consiste en comparar los argumentos de entrada (argv)
con la variable de entorno.

3 Definiciones extraı́das del Diccionario de la Real Academia de la Lengua Española 2001


4 http://ilopez.hugetopia.org/: Ver [7]
34 CAPÍTULO 4. TÉCNICAS DE OFUSCACIÓN DE CÓDIGO

Identificar procesos. La llamada al sistema getsid(pid), funciona de la siguiente manera:

getsid(0): identificador de sesión del proceso invocador


getsid(p): identificador de sesión del proceso con identificador p.

Si comparamos el pid del proceso padre, con el sid de 0, deberı́amos obtener que son iguales
en un entorno normal de ejecución, pero en el caso que seamos ejecutados por un depurador,
esto no sucederá.

4.2.2. Post-compilación
En este caso a partir de un binario se intenta evitar que pueda ser depurado, para evitar ası́ saber
lo que hace. Éstas técnicas son las que evita la herramienta ELFRecover, ya que se basan en evitar
el análisis de un binario que ya ha sido compilado.
En este tipo de modificación se parte de un fichero binario ELF, y el resultado de la transfor-
mación es otro ELF aparentemente distinto, pero cuya funcionalidad se mantiene.

4.3. Modificación de binarios según objetivos


Distinguiremos dos tipos fundamentales de modificación de un binario, la que realiza un virus al
infectar un binario, y la que realiza un atacante al modificar un binario para que no pueda analizarse
lo que hace (ver figura 4.2

Figura 4.2: Tipos de modificación de binario


4.3. MODIFICACIÓN DE BINARIOS SEGÚN OBJETIVOS 35

4.3.1. Código malicioso


Consideraremos código malicioso a todo aquel programa que se ejecute o pueda llegar a ejecutarse
en una máquina sin el permiso o el conocimiento de los administradores. En este apartado se
encuentra el adware 5 , y a él pertenecen los virus, troyanos, spyware, etc.

Infección de binarios

Los virus están considerados como uno de los mayores riesgos a los que se encuentran expuestos
los sistemas de información y como tales deben ser tenidos en cuenta a la hora de hablar de código
malicioso. Por lo general un virus no dejará que un depurador lo analice, para evitar que se pueda
determinar su método de infección y que no sea facil de automatizar su erradicación.
Sobre virus se ha escrito mucho y existe mucha documentación para plataformas mayoritarias,
pero no existe tanta para sistemas Linux. Esta falta de documentación se debe principalmente a
que son sistemas que no estaban orientados −en principio− a uso personal, sino a realizar tareas
de servidor. Con el tiempo Linux ha ido cambiando, se ha ampliado el grupo de gente que utiliza
el sistema operativo y el número de dispositivos que éste soporta y por lo tanto los creadores de
virus también han reparado en el potencial del mismo.
Haremos dos clasificaciones principales de virus, teniendo en cuenta que nos interesa una parte
muy reducida de estos programas. Los virus para ELF que utilizan algún tipo de técnica de ofus-
cación.
Según la técnica de ofuscación que utilizan podemos clasificarlos en:

Código automodificable. El código automodificable se altera a si mismo en tiempo de ejecución,


es por tanto muy complicado analizarlo tanto como proceso como en un fichero binario aislado
sin ser ejecutado, ya que lo que se ve en ambos casos es distinto.

Motores polimórficos. El motor polimórfico crea un único motor de descifrado cada vez que se
usa. Esto quiere decir que el mismo virus tendrá dos formas totalmente diferentes en dos
usos distintos, o incluso en máquinas distintas puede modificarse en función de una serie de
parámetros que el creador del mismo decida.
Los creadores de antivirus utilizan generalmente emuladores para poder determinar qué fun-
ciones realiza un virus determinado, pero mediante técnicas como las comentadas en el aparta-
do de ofuscación antes de la compilación (en este caso teniendo en cuenta detalles de hardware
que generalmente los emuladores no cumplen), no dan el resultado esperado. Por ello hemos
creido necesario desarrollar un método permita utilizar el procesador que es objeto de ataque.

Existe toda una familia de virus para ELF que se basan en la infección de distintas partes del
archivo. Hemos verificado que el formato tiene una serie de caracterı́sticas que le hacen especialmente
vulnerable a dos tipos de infecciones:
5 Sofware añadido a la funcionalidad normal de un programa con fines destructivos, o con fines informativos para
el programador.
36 CAPÍTULO 4. TÉCNICAS DE OFUSCACIÓN DE CÓDIGO

Infección de la PLT. Se puede infectar laa sección del binario que contiene código de resolución
dinámica para hacer las llamadas a las librerı́as compartidas que requiera el proceso. Hay
varios ejemplos de este tipo de virus publicados por Silvio Cesare. El virus Siilov, sustituye
en la PLT las llamadas a la función execve por llamadas a su virus, que posteriormente
llama a execve, si este virus es ejecutado por el administrador, todo el sistema pasa a estar
infectado.

Infección de secciones de datos. Otra posibilidad es infectar una sección de datos. Este metodo
es utilizado también por el virus anteriormente comentado, para añadir código al final de un
binario ejecutable. No cambia el entrypoint, pero sı́ añade un salto a la sección de datos
infectada donde se encuentra su código. Este tipo de infección se puede evitar parcheando el
sistema para que las secciones de datos no sean ejecutables.

Infección del padding entre segmentos y secciones. El código malicioso se sitúa entre los
dos segmentos principales del programa, el de código y el de datos. Es importante destacar
que por motivos de alineacion generalmente hay un espacio entre esos dos segmentos en el
cual se puede poner un código malicioso que se cargará en memoria junto con el resto del
programa y con los permisos adecuados.

4.3.2. Cifrado
En este caso vamos a tratar las modificaciones después de la compilación como medio para
conseguir un binario que no pueda ser analizado con los métodos usuales que proporciona el sistema
operativo. Sin tener en cuenta si el cifrado lo aplica un virus o una persona, hay varias formas de
realizarlo.

Algoritmos de Compresión. El binario es comprimido y se genera un ejecutable autoextraı́ble


que contiene la información mı́nima para ser ejecutado pero no para ser analizado.

Cifrado clásico. A lo largo de toda la historia del cifrado, una técnica muy habitual y efectiva es
la utilización de operaciones XOR con un valor que serı́a considerado la clave. Es una técnica
rápida y efectiva, siempre y cuando se tenga una clave suficientemente fuerte.
4.3. MODIFICACIÓN DE BINARIOS SEGÚN OBJETIVOS 37

Cifradores de bloque. Los cifrados de bloque son utilizados por algoritmos simétricos de cifrado
como DES o AES (ver anexo B) para obtener una información cifrada. Estas técnicas son
incómodas de usar en cifrado de binarios por los requerimientos de alineación, pero son muy
seguras y si se aplican correctamente combinadas con un algoritmo de resumen6 son muy
seguras desde un punto de vista matemático.

En la realización del estudio previo a este trabajo, se han estudiado a fondo dos casos espe-
cialmente representativos en la ofuscación de binarios. Hemos querido que hubiese dos elementos
representativos de los cifradores que utilizan la traducción binaria para proteger la información: uno
que funciona utilizando técnicas de compresión y el otro que utiliza técnicas de cifrado de bloque
combinadas con esquemas de almacenamiento de claves seguros.

Ultimate Packer for eXecutables UPX7 es un compresor de binarios de libre distribución, con
un buen ratio de compresión y que en determinadas ocasiones se utiliza para ocultar el con-
tenido de un binario.
UPX, cuyo objetivo no es cifrar un binario sino comprimirlo, aunque en última instancia se
le dé otros usos, antes de comprimir el binario le elimina las partes prescindibles para su
ejecución, que son las secciones. Después lo comprime y lo incluye en uno nuevo.

Burneye Burneye8 es una herramienta que ha sido desarrollada y distribuida por Teso, un grupo
de desarrolladores de utilidades de seguridad, que pretende proteger un binario ejecutable ELF
de cualquier análisis.
Esta herramienta de cifrado implementa tres tipos distintos de funcionamiento:

Ofuscación. Este tipo de funcionamiento implica la inclusión del binario original en uno
propio de la herramienta que oculta todo el código original al depurador.
Fingerprint. Este modo de funcionamiento extrae información de la máquina donde se va
a ejecutar el binario resultante, y lo ofusca usando como clave esa información. Si el
binario se intenta ejecutar en otra máquina distinta, no será posible.
Clave. En este caso en vez de utilizar como clave información de la máquina, se utiliza una
clave proporcionada por el usuario.

Podemos decir que la figura 4.3 ilustra como se relacionan las distintas capas de Burneye.

6 Ejemplo SHA-1 o MD5 (ver anexo B).


7 http://upx.sourceforge.net/
8 http://teso.scene.at/releases.php
38 CAPÍTULO 4. TÉCNICAS DE OFUSCACIÓN DE CÓDIGO

Figura 4.3: Capas de ofuscación


Capı́tulo 5

La utilidad ELFRecover

Security through obscurity is the best way to fall through


Gema Gómez

ELFRecover es una herramienta de análisis de sistemas comprometidos, que permite recuperar


cierto tipo de binarios modificados con el objetivo de evadir las técnicas tradicionales de depuración.
La mayorı́a de los sistemas de información dependen en gran medida de las herramientas que
proporcionan los sistemas operativos actuales para su gestión y defensa. Cuando se produce un
ataque a menudo los binarios del sistema son suplantados por el intruso y resulta muy difı́cil
determinar qué usos se han dado a esa máquina a parte de los lı́citos y ordinarios.

39
40 CAPÍTULO 5. LA UTILIDAD ELFRECOVER

5.1. Introducción
Utilizaremos la herramienta ELFRecover para el contexto de una máquina comprometida en
que se intenta realizar un análisis exhaustivo de lo sucedido, analizar aquellos binarios que resulte
difı́cil depurar con las técnicas clásicas.
Hemos optado por un mecanismo mı́nimamente intrusivo, que por un lado analiza el binario
para ver si podrá dar solución al problema que plantea, y por otro permite −en muchos casos−
recuperar el binario en su forma original, antes que el atacante lo modifique para su conveniencia.
Esto será posible siempre y cuando el tipo de ofuscación sea post-compilación (ver 4.2.2) con
una de las herramientas cuya funcionalidad hemos comentado en 4.3.2.
El objetivo de este trabajo es utilizar una técnica suficientemente genérica como para poder
aplicarla en casos en los que hasta ahora se tenı́a que utilizar el kernel para realizar estos análisis.
No requiere modificaciones excesivamente costosas para el analista o programador, y el programa
se encuentra simultáneamente en memoria con el código analizado, para poder controlar qué hace
y detenerlo en caso necesario.

Figura 5.1: Esquema de funcionamiento ELFRecover

La herramienta consta de tres elementos diferenciados, con objetivos diferenciados.

ELFVerify es una utilidad que analiza un binario y nos da información relevante al respecto del
mismo.

ELFRecover.so.1 es una librerı́a compartida que realiza la comprobación en tiempo de ejecución


y descifrado de binarios, ası́ como de recuperación de los originales.

ELFRecover no hace uso de criptoanálisis ni rompe ningún algoritmo criptográfico, sino que
intenta evitar las técnicas de ofuscación que se utilizan para que un binario no pueda ser analizado
5.2. LA HERRAMIENTA ELFRECOVER 41

por las herramientas convencionales de análisis de código ejecutable.


Hemos utilizado la Ingenierı́a Inversa sobre aplicaciones conocidas para ver qué tipo de al-
goritmos siguen, que técnicas y métodos usan para ofuscar los binarios −en este caso−, y como
consecuencia hemos aportado una posible solución al problema que plantean.
Linux es un sistema de código abierto, cuyas especificaciones son también abiertas y están
a disposición de toda la comunidad. Hemos estudiado la carga de procesos en Linux, sobre todo
dinámica, que es la más innovadora desde el punto de vista de los sistemas operativos, pero también
la estática, ya que en el entorno en el que se mueve nuestra herramienta será de vital importancia
la forma en que los procesos son cargados y ejecutados por el kernel y el enlazador dinámico.
Queda fuera de este trabajo aportar una solución para todos los casos posibles, dado que cada
tipo de ofuscación requiere de un tipo de análisis distinto.

5.2. La Herramienta ELFRecover


Los binarios que van a ser analizados en sistemas comprometidos tienen unas caracterı́sticas
muy concretas, generalmente tendrán el bit de suid a cierto, para poder ejecutarse con privilegios
de administrador y serán binarios aparentemente enlazados estáticamente, por tanto con toda la
información necesaria para ser ejecutados incluida, aunque veremos que amenudo las apariencias
engañan y en última instancia sı́ que se realizará una carga dinámica. Para los binarios suid root
es necesario que se ejecute el programa con permisos de administrador, ya que en caso contrario
Linux no tienen en cuenta la variable LD PRELOAD y no cargará la librerı́a ELFRecover.

5.2.1. Detector de anomalı́as: ELFVerify


Programa que analiza el binario objeto de estudio e intenta determinar si cumple con el patrón
de binario cifrado o no.
Realiza una previsión a partir de la cabecera del binario y determina si está cifrado en base a
una heurı́stica que tiene en cuenta una serie de caracterı́sticas de cifradores gratuitos que pueden
encontrarse por Internet. Además informa de si el programa ELFRecover será o no de utilidad
según el tipo de cifrado detectado.
42 CAPÍTULO 5. LA UTILIDAD ELFRECOVER

Algoritmo de ELFVerify

1. Comprueba que el fichero sea de tipo ejecutable, sin lo cual no tiene sentido un análisis basado
en el criterio explicado a continuación.

2. Verificar que las condiciones son adecuadas para un binario generado por un compilador
estándar. El programa comprueba que el archivo sea estático, basándose en los tipos de seg-
mentos que tiene, la condición para que lo sea es que aparezca un segmento PT INTERP, que
indicará cuál es el intérprete que se utiliza para cargar el binario y uno PT DYNAMIC, que
contendrá información de donde se encuentra la tabla de hash, la función init, fini, etc.
Si el binario da positivo como dinámico, no está utilizando una de las técnicas estudiadas.

3. Determinar las caracterı́sticas generales del binario, en función de la información de la cabecera.


Si la información de depuración de la cabecera no existe (los campos e shnum y e shentsize
están a 0) y el punto de entrada se encuentra por debajo del esperado (sobre la dirección
0x05370000), la herramienta determina que es un Burneye, y si el punto de entrada es normal
sugiere que puede estar cifrado pero podrı́a ser un falso positivo.

4. Generar resultados.

Utilización

El programa ELFVerify, es una herramienta en lı́nea de comandos Linux, que tiene como fichero
de entrada el que va a ser sometido a análisis.
Se usa ejecutando el programa como se muestra a continuación:

$ ./ELFVerify [-i inputfile] [-h]

ELFVerify intenta determinar por qué no es posible depurar el programa y hará una previsión
sobre el éxito de ELFRecover.so.1 en su recuperación.

5.2.2. Modificar y revisar el binario: ELFRecover.so


Recuperación del binario en caso de que sea posible. Teniendo en cuenta el proceso de inves-
tigación realizado, podremos recuperar un binario que haya sido cifrado con estas herramientas
siempre y cuando originariamiente fuera dinámico.

Algoritmo de ELFRecover.so

1. Determinar las posiciones en las que está mapeado el programa, en el procfs. El programa
lee del archivo /proc/self/maps las direcciones de inicio y final teóricas del programa.

2. Detectar si alguna cadena coincide con las de los cifradores conocidos. Las condiciones en
tiempo de ejecución son distintas de las estáticas. En el caso de Burneye mira también el
punto de entrada a partir del mapeo en memoria y si se encuentra entre las direcciones de
5.2. LA HERRAMIENTA ELFRECOVER 43

este cifrador da positivo. En el caso de UPX, es detectado porque aparentemente es un proceso


que corre a partir de un binario inexistente (UPX realiza una copia del binario descifrado en
/tmp y antes de cederle el control lo borra, situación que permite que le detectemos).

3. Buscar el binario adecuado en memoria, buscando un magic number correcto, y calcular el


espacio que ocuparı́a el disco a partir de la información de sus cabeceras reales.

4. Escribir el binario recuperado a disco.

5. Detener la ejecución.

6. En caso que el binario no sea uno de los potencialmente peligrosos, se puede seguir normal-
mente con la ejecución.

Utilización

Esta librerı́a debe ser cargada en memoria y recupera el binario antes de su ejecución. Te-
niendo en cuenta que el tipo de detección que se realiza en algunos casos es distinta de la que
realiza ELFVerify, ya que tiene información sobre el proceso además de tenerla sobre el binario
propiamente.
Hay que tener en cuenta, en el caso de análisis de binarios cifrados que sean suid root y deban
analizarse, es necesario que se haga con privilegios de administrador. Es recomendable no utilizar
una máquina en producción, sino un entorno de pruebas.
Se usa de la siguiente manera:
1. Se pone la librerı́a como requerimiento a la hora de cargar un programa, teniendo en cuenta
que hay que proporcionar el directorio absoluto. Si tuviesemos la librerı́a en el directorio
/home, se realizarı́a de la siguiente manera:

$ export LD_PRELOAD=/home/ELFRecover.so.1

2. Se ejecuta el binario analizado por ELFVerify, para que la librerı́a pueda intentar recuperarlo.
Si el binario puede ser descifrado tendremos una copia del mismo en disco para su posterior
análisis en disco. Es recomendable realizar esta acción en un entorno seguro de ejecución,
sin privilegios de root, ya que en cualquier caso el código ejecutado por el programa puede
ser malicioso y hay que minimizar los riesgos. Se recomienda un entorno enjaulado (Ver la
apliación chroot) o una máquina aislada de prueba para llevar a cabo la recuperación.

3. Cuando se termine la ejecución, devolver el sistema a su estado original:

$ unset LD_PRELOAD

Por comodidad hemos añadido un script llamado decipher.sh que realiza las tareas de ac-
tualizar con el valor adecuado LD PRELOAD y de lanzar el programa. Funciona de la siguiente
manera:

$ ./decipher.sh <fichero_cifrado>
44 CAPÍTULO 5. LA UTILIDAD ELFRECOVER
Capı́tulo 6

Análisis de resultados

Cuando se consigue una buena ejecución,


se cree haber encontrado todos los errores.
No es verdad.
Truck Smith, Los secretos de la depuración del software

En este capı́tulo se exponen y analizan los resultados obtenidos por la herramienta ELFRecover
frente a los distintos binarios con los que ha sido probada, y se explican las particularidades de cada
uno de ellos.
Hay que tener en cuenta que cada kernel es distinto, por eso hemos probado en versiones 2.2 y
2.4, y cada distribución tiene sus particularidades por tanto se ha probado en algunas de las más
significativas.

45
46 CAPÍTULO 6. ANÁLISIS DE RESULTADOS

6.1. Introducción
Los juegos de pruebas relacionados con esta aplicación no constan solamente de probar el re-
sultado de la misma en los distintos entornos y kernels, sino también de ver el comportamiento de
las dos aplicaciones para las cuales ha estado diseñada. UPX y Burneye utilizan técnicas distintas
de cifrado como hemos visto, la primera se basa en un algoritmo de compresión y la segunda en 3
algoritmos fundamentales, uno de ofuscación, otro de cifrado con contraseña, y otro de fingerprint
para evitar que la aplicación se pueda ejecutar en otra máquina distinta de aquella para la cual fue
generado.

6.1.1. Juegos de Pruebas


Hemos jugado con una aplicación simple, un hola mundo que tiene el siguiente código. Supon-
dremos que nos encontramos en una máquina comprometida un binario como los que mencionamos
más adelante, e intentaremos recuperarlos con la herramienta ELFRecover en distintos entornos.

int main(){
printf("Hola mundo");
}

Partimos de dos archivos binarios generados con gcc a partir de ese código, uno estático y otro
dinámico:

1. Archivo estático ofuscado con Burneye.

2. Archivo dinámico ofuscado con Burneye.

3. Archivo estático cifrado con Burneye con contraseña.

4. Archivo dinámico cifrado con Burneye con contraseña.

5. Archivo estático con fingerprint Burneye para ejecutarse en la máquina para la que se com-
piló nada más.

6. Archivo dinámico con fingerprint Burneye para ejecutarse en la máquina para la que se com-
piló nada más.

7. Archivo estático cifrado con UPX.

8. Archivo dinámico cifrado con UPX.

Y hemos procedido sistemáticamente probando en qué casos somos capaces de recuperar el


binario completamente.
6.2. CIFRADOS 47

6.2. Cifrados
Ambos cifradores se basan en confundir al depurador incluyendo una nueva cabecera ELF
estática, que apunta a un código añadido por el cifrador. Ése código se encarga de realizar el
descifrado y después cede el control al programa original, en ese momento en memoria está el
código que nos interesa recuperar, y en ese momento se carga ELFRecover y recupera el binario.
La librerı́a es totalmente operativa con los cifrados que incluyen dentro un binario dinámico,
dado que se basa en una técnica que requiere de un entorno dinámico de ejecución para funcionar.
Si el binario incluido por el empaquetador es estático no funcionará. En esta lı́nea de resolución del
problema, desarrollamos una aplicación que convertı́a el entorno estático a dinámico mediante otro
binario externo.
Pero el método no dió los resultados esperados debido principalmente a que la librerı́a no está car-
gada en un momento en el que el código esté en claro en memoria. Como apuntaremos en el capı́tulo
de nuevas lı́neas de investigación, la solución a este problema está en recuperar el proceso una vez
esté funcionando, con la consiguiente reconstrucción de su zona de datos, que ya habrá sido alterada.
Por tanto el juego de pruebas final queda con la siguiente lista, (ver en la figura 6.1 el tipo
de binarios que son):

Figura 6.1: Ofuscación de un binario

1. Archivo dinámico ofuscado con Burneye.

2. Archivo dinámico cifrado con Burneye con contraseña.


48 CAPÍTULO 6. ANÁLISIS DE RESULTADOS

3. Archivo dinámico con fingerprint Burneye para ejecutarse en la máquina para la que se com-
piló nada más.

4. Archivo dinámico cifrado con UPX.

6.2.1. RedHat IA-32

Burneye

En este caso tenemos 3 resultados:

Archivo dinámico ofuscado con Burneye. La recuperación es completa y satisfactoria (ver


figura 6.2).

Archivo dinámico cifrado con Burneye con contraseña. La recuperación no puede llevarse a
cabo a no ser que se conozca la contraseña, debido a que la comprovación se realiza pre-
viamente a la carga del programa dinámico y a que la clave se almacena como un resumen
criptográficamente fuerte (SHA-1, ver anexo B).

Archivo dinámico con fingerprint Burneye. La recuperación es completa y satisfactoria, pero


hay que tener en cuenta que debe realizarse en la máquina para la que el binario con fingerprint
fue generado, (ver figura 6.2).

Figura 6.2: Descifrado en el caso de Burneye


6.2. CIFRADOS 49

UPX

En este caso hemos obtenido un resultado fuertemente satisfactorio, pero el packer ha compri-
mido el archivo hasta el punto de dejarlo sin vista de secciones y no se ha podido recuperar esa
parte, por lo tanto la recuperación no es del todo completa, aunque el archivo podrá analizarse
como un archivo generado sin la información de depuración. Nótese la falta de la parte de secciones
(vista de depuración) en la figura 6.3.

Figura 6.3: Descifrado en el caso de UPX

6.2.2. RedHat IA-64


A pesar de no existir código malicioso para arquitecturas IA-64 todavı́a, hemos hecho las pruebas
en una máquina cuyo kernel soporta la ejecución de código de 32 bits. Por lo tanto, aunque la
aplicación compila perfectamente para IA-64, no tenı́amos código malicioso con el que probarla.
Hay que tener en cuenta que mientras las máquinas Itanium se configuren por motivos de
compatibilidad con soporte para ELF32, tendrán los mismos problemas que las máquinas de 32 bits
en cuanto a aplicaciones subversivas.

Burneye

En este caso hemos probado a recuperar los binarios generados para la anterior prueba y que la
pasaron con éxito −con la librerı́a compilada para 32 bits−:

Archivo dinámico ofuscado con Burneye. La recuperación es completa y satisfactoria.


50 CAPÍTULO 6. ANÁLISIS DE RESULTADOS

Archivo dinámico con fingerprint Burneye. Esta recuperación no se pudo llevar a cabo, dado
que no pudimos generar un binario cifrado, la aplicación Burneye no funciona plenamente
sobre la arquitectura.

UPX

Los binarios empaquetados con UPX no pudieron generarse en esta arquitectura, y analizando el
código notamos que era debido a que la aplicación ha sido programada a nivel de registros.

6.2.3. Debian y Gentoo


En ambos casos el programa se comporta igual que el caso del apartado 6.2.1, ha sido probado
en kernels 2.2 y 2.4.

6.3. Conclusión
En respuesta a la pregunta que nos ha movido a realizar este trabajo, aquella que plantea si
es posible saber lo que hace una aplicación si se tiene que ejecutar en una máquina, la respuesta
es que sı́, pero que dependemos en gran medida de la manera en que el sistema operativo trata la
carga de los binarios, ya que cuando existe un esquema de cifrado con criptografı́a fuerte, como en
el caso de Burneye con contraseña, no es posible atacarlo de otra forma que por fuerza bruta. En
todo caso, es una excepción, ya que no se tiene el programa completo hasta que se consigue la clave
de cifrado.
En cualquier caso, habrı́a que pensar en nuevas técnicas de captura de código en tiempo de
ejecución.
Capı́tulo 7

Conclusiones y lineas abiertas de


investigación

When you know a thing, to hold that you know it;


and when you do not know a thing,
to allow that you do not know it...
this is knowledge
Confucius, The Confucian Analects

Este capı́tulo pretende dar a conocer cuáles han sido los objetivos alcanzados mediante la In-
vestigación y Desarrollo llevados a cabo durante el proyecto. Asimismo remarcamos una serie de
aspectos que serán relevantes para aquellos que quieran continuar el trabajo en el punto que lo de-
jamos, o mejorar las técnicas aquı́ propuestas en pro de una mejora sustancial en las herramientas
de análisis forense de aplicaciones existentes.

51
52 CAPÍTULO 7. CONCLUSIONES Y LINEAS ABIERTAS DE INVESTIGACIÓN

7.1. Conclusiones
Dado que el diseño de sistemas operativos está cada vez más condicionado por los requerimientos
de seguridad, hemos visto conveniente aportar un poco de luz al cifrado de binarios con fines
deshonestos, para evitar ası́ que el código malicioso se extienda por las redes sin control.
Nos encontramos en un punto intermedio entre los sistemas operativos que ejecutan código
arbitrariamente, y aquellos que lo harán basándose en sistemas de código firmado y autenticado por
el fabricante. Mientras las Infraestructuras de Clave Pública1 necesarias son generadas, y aparecen
formatos de binario que soporten firmas digitales, ası́ como sistemas operativos que solamente
ejecuten los binarios firmados y verificados, seguiremos teniendo este tipo de problemas.

7.2. Objetivos conseguidos y aportaciones


La elaboración del proyecto fin de carrera con tı́tulo Ingenierı́a Inversa para Binarios tenı́a
como objetivos:

Determinar por qué algunas aplicaciones son difı́iciles de analizar por los depuradores exis-
tentes. Apuntar cuáles son las causas y consecuencias de ello.

Dejar claramente documentado que este tipo de prácticas existen y qué puede significar que
alguien se encuentre un binario de estas caracterı́sticas en su máquina.

Desmitificar cierto tipo de código malicioso. Aportar documentación suficiente sobre el tema
como para que cualquier técnico que se encuentre ante un programa de estas caracterı́sticas
sea capaz de abordar el problema de forma eficaz y coherente.

Aportar algun tipo de herramienta, en este caso ELFRecover, que consiga dar un paso para
poder analizar cualquier tipo de binario que se quiera ejecutar en una máquina.

Estos objetivos han sido cumplidos, no sin ciertas limitaciones, que como veremos en la sección
de lı́neas abiertas de Investigación, son el inevitable paso siguiente en la carrera de ganar la partida
al código malicioso.
En concreto la aportación de este proyecto, en terminos de documentación es:

Documentación de técnicas de ofuscación de binarios ELF.

Documentación de técnicas de infección de binarios ELF, y posibles soluciones a la ejecución


arbitraria de código malicioso.
1 En inglés PKI: Public Key Infrastructure.
7.3. LÍNEAS ABIERTAS DE INVESTIGACIÓN 53

Documentación del proceso de carga de binarios en sistemas Linux, ası́ como detallado de
puntos clave que hacen complicado el análisis de cierto código.

Y la aportación del proyecto en cuanto a software desarrollado es:

Diseño y desarrollo de una aplicación que, basándose en una heurı́stica resultante de la ob-
servación de las caracterı́sticas de cierto código cifrado, hace una previsión del tipo de cifrado
al que ha sido sometido un binario ELF. En sus versiones de 32 y 64 bits.

Diseño y desarrollo de una librerı́a dinámica que cargada junto con el binario a analizar,
realiza las labores de detección y descifrado de dos técnicas de cifrado que se utilizan por
algunos troyanos y aplicaciones maliciosas actualmente.

Integración en una herramienta de ambos componentes de manera que faciliten la tarea de


expertos analistas de sistemas comprometidos.

7.3. Lı́neas abiertas de investigación


Las lı́neas abiertas de investigación van encaminadas en dos tendencias diferenciadas: por un
lado el diseño de herramientas capaces de realizar análisis de código cifrado y recontruir procesos
a partir de su imagen en memoria para el análisis forense de sistemas comprometidos, y por otro
el desarrollo de sistemas operativos en los que solamente se ejecute código firmado y verificado por
un tercero de confianza, para poder erradicar la ejecución de código malicioso en el futuro.

7.3.1. Reconstrucción de binarios a partir de procesos


En el caso de ELFRecover.so.1, la reconstrucción del proceso de memoria se realiza justo
despues de la carga, y antes que se realice ninguna resolución en la Global Offset Table. Sin embargo,
cuando se realiza el análisis de un sistema comprometido, no siempre se tiene la suerte de encontrar
el binario ELF que se ha utilizado para lanzar un proceso.
Esta situación unida a que la configuración segura de los sistemas hace que no se pueda volcar
la memoria a disco por los métodos tradicionales, obliga a encontrar métodos a partir de los cuales
se pueda conseguir un binario ejecutable partiendo de su imagen en memoria.

7.3.2. Ejecución de código de confiable


Pese a que actualmente se estan estudiando y desarrollando sistemas basados en código firmado,
todavı́a no existe una versión realmente operativa y eficiente debido a que esto implica que los
usuarios entiendan qué es un código firmado y cómo debe llevarse a cabo la verificación.
54 CAPÍTULO 7. CONCLUSIONES Y LINEAS ABIERTAS DE INVESTIGACIÓN

La ejecución de código confiable implica una concienciación profunda por parte del usuario de
lo que es la firma electrónica. Todavı́a no estamos del todo familiarizados con este concepto, ni con
las infraestructuras que se requieren para que un sistema similar tenga éxito.

Figura 7.1: Binario firmado digitalmente

Los requerimientos de este sistema son:

1. Autoridades de Certificación que garanticen que los firmantes de código son quienes dicen ser
y que además sus claves siguen en vigor2 .

2. Formatos de binario que permitan incluir la información que muestra la figura 7.1. Vemos
que es necesario por un lado la firma del binario para garantizar que ese código no ha sido
modificado por terceros con posterioridad a que el fabricante lo firmase. La clave pública y
la firma por parte de la Autoridad de Certificación para garantizar que esa clave es de quién
parece ser. Y el certificado para poder verificar si esa firma es válida o ha sido invalidada por
motivos de seguridad.

3. Conocimiento por parte de los usuarios de cómo verificar un programa y cuál es el criterio
para ejecutarlo o no en función de la verificación de la firma.

Pese a no existir una estandarización en la firma de código ejecutable, tanto Microsoft (con
la tecnologı́a Authenticode) como Sun Microsystems (con su tecnologı́a Jarsigner ), han tomado
iniciativas en este sentido.
Las limitaciones principales para la aceptación de este tipo de prácticas es por un lado la
concienciación de los diseñadores de sistemas operativos y por otro la de los usuarios.

2 Las Autoridades de Certificación mantienen listas consultables en todo momento de los certificados que han sido
revocados por motivos de seguridad.
Apéndice A

Consideraciones éticas y legales

If you think technology can solve your security problems,


then you don’t understand the problems,
and you don’t understand the technology
Bruce Schneier, Secrets & Lies

En el mundo de la Seguridad Informática en particular, y de la Informática en general, todavı́a


nos movemos entre la legalidad y la ilegalidad, y echamos en falta un código ético que explicite la
buena o mala fe, que explique qué está bien y qué está mal.
Como en cualquier otro tipo de herramienta, informática o no, la calificación moral depende del
uso que se le da.
Dado que hablamos de una disciplina relativamente nueva, es lógico que todavı́a no esté claro
ni el marco legal que envuelve los asuntos de los que tratamos en este capı́tulo, ni las consecuencias
que se derivan de algunas acciones. Intentaremos aproximar las consecuencias que podrı́a tener una
herramienta -como la explicada en este documento- en el marco legal actual, sin perder de vista
que es un tema con muchos intereses contrapuestos.

55
56 APÉNDICE A. CONSIDERACIONES ÉTICAS Y LEGALES

A.1. Introducción
En nuestra opinión la ingenierı́a es uno de los campos de la ciencia que se encarga de gestionar,
definir y optimizar todo tipo de procesos industriales, mecánicos, informáticos... en definitiva todos
aquellos que requieran de una metodologı́a formal para ser llevados a cabo. Generalmente a los
cientı́ficos les interesa el por qué, y a los ingenieros el cómo.
En cuanto a las leyes relacionadas con el trabajo de los Ingenieros en Informática, que es la
profesión que nos ocupa, todavı́a hay vacı́os legales, sobre todo en cuanto a definir qué prácticas
son lı́citas y cuáles no lo son.

A.2. Ética
La velocidad a la que avanza la tecnologı́a, mucho más rápidamente que cualquier cuerpo legis-
lador, ha dado lugar a los códigos éticos, en tanto en cuanto nos ayudan a comportarnos de forma
correcta, o garantizan que la gente que lo suscribe, tiene una tendencia general a comportarse de
una determinada manera. Últimamente están surgiendo una serie de ellos orientados a regular el
tratamiento automatizado de datos, las empresas tienen la opción de suscribir uno u otro compor-
tamiento.
La ética en las nuevas profesiones, como en todas juega un papel muy importante. Los profesio-
nales se encuentran ante decisiones que afectan a los derechos de otras personas u entidades y deben
tomarlas teniendo en cuenta únicamente sus propios principios −supóngase un administrador que
es obligado a poner un filtro en el correo de todos sus compañeros en una empresa−.
Se nota especialmente este vacı́o legal en los temas en que es necesario aplicar Ingenierı́a Inversa,
y en concreto no queda demasiado claro cuándo puede considerarse piraterı́a y cuándo no.

A.3. Ingenierı́a Inversa


En la sección 1.4 dimos una definición de Ingenierı́a Inversa, en el contexto de los sistemas
comprometidos.
Esta situación hace que sea necesario automatizar los procesos de análisis de software, para poder
ası́ detectar virus, troyanos, spyware −código orientado a espiar en la máquina que se ejecute− de
forma automática y recuperar el código.
En el momento que exista un software capaz de realizar las funciones de análisis y recuperación
del código inicial con relativo éxito, los procesos antivirus podrán dejar de realizarse en base a algo-
ritmos estadı́sticos (heurı́sticas) como hasta ahora hacı́an para detectar virus nuevos o polimórficos,
o incluso para analizar software de dudosa procedencia antes de ejecutarlo en una máquina.
A.3. INGENIERÍA INVERSA 57

A.3.1. Piraterı́a

La piraterı́a, también conocida como cracking de aplicaciones, utiliza la Ingenierı́a Inversa para
conseguir programas de los cuales no se posee licencia. Los creadores de software se esfuerzan
en proteger sus aplicaciones mientras que los piratas informáticos se esfuerzan en saltarse esas
protecciones. No es un tema en el que queramos entrar, ni tampoco en el precio del software. Los
objetivos de este proyecto en ningún momento han sido otros que analizar binarios protegidos con
fines poco honestos, sin entrar en la discusión de si uno tiene derecho a saber qué hace un programa
comprado en su máquina.
Además debemos resaltar que cuando se compra el uso de un producto, se compra también el
derecho de realizar Copias de Seguridad.
Es representativo el caso de Dimitry Sklyarov, un programador ruso que el verano 2002
fue detenido en Estados Unidos cuando iba a explicar en una conferencia cómo se habı́a saltado
la protección de los e-book de Adobe. La Electronic Frontier Foundation se puso de su parte y
Adobe cedió en retirar los cargos si hacı́a de testigo cuando se realizase el juicio contra su empresa.
Dimitry fue puesto en libertad en Diciembre de 2002, a la espera del juicio contra la compañı́a para
la que trabaja, ELCOMSOFT, que es la que comercializa ese software. En ningún momento ha sido
mencionado que el algoritmo que rompió Dimitry, era un Rot13, que es un algoritmo basado en
códigos César que suma 13 unidades a cada carácter que cualquier criptoanalista lo habrı́a podido
romper en cuestión de minutos.

A.3.2. Opiniones

Pamela Samuelson [16], en su artı́culo sobre la legalidad de la Ingenierı́a Inversa de Octubre de


2002, apunta que la sociedad estadounidense actual considera este tipo de prácticas como beneficio-
sas para el progreso, ya que obligan a mantener activa la investigación y el desarrollo y funcionan
como motor de la tecnologı́a. A pesar de todo, hay una sentencia pendiente −de las Cortes Supre-
mas de California− sobre la decodificación por parte de Andrew Bunner de un sistema de control
de copias de DVD, y su posterior publicación en una web de la aplicación que lo permite.
La cuestión importante está en el uso que se hace de la herramienta, más que en el uso que se
podrı́a hacer de ella.
Cristina Cifuentes, de Sun Microsystems Laboratories, en su artı́culo [3] hace una reflexión del
estado del arte de la Ingenierı́a Inversa, a finales del 2001.
Además entiende que siempre hay casos en los que es necesario utilizar técnicas de Ingenierı́a
Inversa, por ejemplo cuando se trata de realizar traductores binarios para poder pasar de una
plataforma a otra aplicaciones, o cuando se trata de optimizar la ejecución de ciertos programas.
58 APÉNDICE A. CONSIDERACIONES ÉTICAS Y LEGALES

A.3.3. Análisis de Sistemas Comprometidos, el peritaje


El peritaje de sistemas informáticos comprometidos es una de las aplicaciones más directas de
la Ingenierı́a Inversa. Consiste en analizar un sistema, tanto fı́sica como lógicamente, para conseguir
reconstruir una serie de hechos que han tenido lugar en el pasado.
Este tipo de prácticas se lleva a cabo pocas veces en España, principalmente a que es una
práctica muy nueva y todavı́a no hay legislación suficiente al respecto. Además requiere de una
preparación altamente técnica tanto del périto que realiza el análisis como del abogado que lleva el
caso.
Dada la mentalidad europea, se tiende a menudo a no denunciar este tipo de delitos, por la mala
imagen corporativa que puede derivarse de tal denuncia, en una sociedad que todavı́a no está del
todo familiarizada con este tipo de hechos. Sin embargo, la búsqueda de evidencias en sistemas
comprometidos es una práctica común cuando se produce una intrusión en un sistema crı́tico.
Apéndice B

Algoritmos criptográficos

Damos una breve descripción de los algoritmos criptográficos mencionados en este trabajo.
Algoritmo criptográfico fuerte es aquel que no puede ser atacado por otro método que la prueba
sistemática de todas las claves posibles. En el caso de utilizarse un algoritmo de estas caracterı́sticas
para cifrar un binario, y si no se incluye la clave en texto claro en el archivo, no será posible recuperar
su contenido y ejecutarlo.

B.1. Algoritmos Simétricos


El objetivo de los algoritmos simétricos es proveer confidencialidad a unos datos.
Su caracterı́stica principal es que tienen un parámetro de longitud N como clave. Dada una clave
determinada y un texto de entrada, la función que calcula el cifrado solamente tiene una imagen.
Además es invertible, a partir de un texto cifrado se puede obtener el original.
Este tipo de cifradores son rápidos, y cifran por bloques. Es posible utilizar múltiples cifrados
para aumentar la seguridad, pero no siempre se consigue el efecto deseado, esto depende de las
caracterı́sticas del algoritmo.
Los algoritmos mencionados de este tipo que hemos mencionado son:

DES: Este algoritmo fue diseado en 1970 por IBM y ha sido una norma de cifrado internacional
hasta hace poco. No ha sido substituido porque se le haya atribuido ninguna vulnerabilidad,
sino porque su espacio de claves ha dejado de ser un problema para la capacidad de cálculo
de los procesadores actuales. Utiliza una clave de 56 bits.

AES: Este algoritmo se ha erigido como nuevo estándard por el NIST (Instituto Nacional de Nor-
malizacin y Tecnologa), su espacio de claves es mayor que DES, ya que puede cifrar con claves
de 128, 192 o 256 bits.

59
60 APÉNDICE B. ALGORITMOS CRIPTOGRÁFICOS

B.2. Hash
Los algoritmos de hash o de resumen, se utilizan para calcular un resumen criptográfico a partir
de una secuencia de datos de longitud arbitraria. El resumen siempre tiene la misma longitud.
Dados un texto determinado y su resumen, no es posible encontrar otro texto que tenga como
resultado el mismo resumen.
Los algoritmos de este tipo mencionados en este trabajo son SHA-1 y MD5. El algoritmo SHA-1
se está utilizando es esquemas de firma electrónica.
Este tipo de algoritmos provee de integridad, ya que hace posible comprobar si unos datos han
sido modificados o no, después de haber calculado y guardado convenientemente su resumen.
Bibliografı́a

[1] Bob Neveln. “Linux Assembly Language Programming”. Prentice Hall PTR, July 2000.

[2] Bruce Schneier. “Secrets & Lies” John Wiley & Sons, Inc; 2000.

[3] Cristina Cifuentes, Sun Microsystems Laboratories


c . “Reverse Engineering and the Com-
puting Profession”. Computer, December 2001

[4] David Dittrich, Ervin Sarkinsov. “Pasos básicos en el Análisis Forense de Sistemas GNU/Linux,
UNIX”. Contribución Congreso Hispalinux.

[5] Intel Corporation. “Intel ItaniumT M Processor-specific Application Binary Interface (ABI)”.
May 2001.

[6] Intel Corporation. “System V Application Binary Interface. Intel386T M Architecture”. Pro-
cessor Supplement, Fourth Edition, 1997.

[7] Iñaki López. “Nuevas técnicas de detección de depuradores”. Agosto del 2002.

[8] F. J. Monserrat y J. M. Navarro. “Máquinas trampa y análisis forense”.


Boletı́n de RedIRIS n. 61, Septiembre 2002.

[9] Galderic Punti, Marisa Gil, Xavier Martorell, Nacho Navarro. “gtrace: function call and mem-
ory access traces of dynamically linked programs in IA-32 and IA-64 Linux ”.
UPC-DAC-2002-51, November 2002.

[10] John R. Levine. “Linkers & Loaders”. Morgan Kaufmann Publishers, 2000.

[11] Jonathan Corbet, Alessandro Rubini. “Linux Device Drivers, 2nd Edition”. O’Reilly, June
2001.

[12] Jose Luis Balcázar, “Programación Metódica”. Mc Graw-Hill, 1993.

[13] Juan López Rubio. “Speedy: Traductor Binario y Optimizador de Binarios Dixie”. Proyecto
de Ingenierı́a Informática de la Universidad Politécnica de Catalunya, 28 Junio 2002.

61
62 BIBLIOGRAFÍA

[14] M. Serna, C. Àlvarez, R. Cases i A. Lozano. “Els lı́mits de la computació. Indecidibilitat i


NP-completesa” Edicions UPC, 2001

[15] Marius van Oers. “Unix Shell Scripting Malware”. McAfee AVERT, The Netherlands. Virus
Bulletin 2002.

[16] Pamela Samuelson. “Reverse Engineering Under Siege”. Computer, October 2002.

[17] Pekka Himanen. “La ética del hacker”. Ediciones Destino, Colección Imago Mundi,
Volumen 3.

[18] Peter Ször and Peter Ferrie. “Hunting for Metamorphic”. Virus Bulletin Conference, September
2001.

[19] Roger A. Grimes. “Malicious Mobile Code: Virus Protection for Windows”. O’Reilly, August
2001.

[20] The Grugq. “Subversive Dynamic Linking to Libraries”.

[21] Tool Interface Standard (TIS) Portable Formats Specification v1.1


http://x86.ddj.com/ftp/manuals/tools/elf.pdf

[22] Yannis Smaragdakis. “Layered Development with (Unix) Dynamic Libraries”. ICSR 2002.

[23] Warren W. Gay. “Advanced Unix Programming”. Sams Publishing, September 2000.

[24] Leslie Lamport “A document Preparation System: LATEX. User’s guide and reference manual”,
Addison-Wesley, 1994.

Vous aimerez peut-être aussi