Académique Documents
Professionnel Documents
Culture Documents
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
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
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
B. Algoritmos criptográficos 59
B.1. Algoritmos Simétricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
B.2. Hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
vi ÍNDICE GENERAL
Capı́tulo 1
Introducción
¿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.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.
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.
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
Los pasos que sigue un atacante cuando planea hacerse con un sitio determinado, son los
siguientes:
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.
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.
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.
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.)
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−.
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
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.
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.
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.
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.
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
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
Un archivo reubicable, es aquel que contiene código y datos para ser montados y crear un
ejecutable o un objeto compartido.
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:
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.
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
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 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.
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 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 )
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
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.
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
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
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 align: Este atributo proporciona el valor al cual los segmentos están alineados en memoria y en
el archivo.
Nombre Valor
PT NULL 0
PT LOAD 1
PT DYNAMIC 2
PT INTERP 3
PT NOTE 4
PT SHLIB 5
PT PHDR 6
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.
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;
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
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.
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
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.
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
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.
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
3. Inicializar a cero las partes del código de datos no inicializados, si la memoria virtual del
sistema no lo hace automáticamente.
5. Inicializar las variables que tiene valores inicialmente, ası́ como los argumentos de programa
y las variables de entorno.
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
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.
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
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.
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 :
Cifrar es transcribir en guarismos, letras o sı́mbolos un mensaje cuyo contenido se quiere ocultar.
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.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 .
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.
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.
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:
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.
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.
La utilidad ELFRecover
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.
ELFVerify es una utilidad que analiza un binario y nos da información relevante al respecto del
mismo.
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
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.
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 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.
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
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.
$ 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
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.
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:
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.
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):
3. Archivo dinámico con fingerprint Burneye para ejecutarse en la máquina para la que se com-
piló nada más.
Burneye
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).
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.
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 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.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
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.
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 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.
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.
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.
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
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.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
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.
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.
[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.
[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.
[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
[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.
[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.