VIRUS INFORMÁTICOS: Y así empezó todo... Autor: Juan-Mariano de Goyeneche. Publicado en el Número 6 de Programación Actual. Virus. Todo el mundo ha oído hablar de ellos y muchos los han sufrido, pero su forma de infectar, reproducirse o atacar sigue siendo un gran misterio para la mayoría. Una antigua máxima reza: "Si quieres vencer a tu enemigo, conócelo primero". Eso es lo que se intentará en esta entrega: si se ejecutan en un ordenador, es que son programas. Y si son programas, se pueden comprender. Es cierto que no contamos con el código fuente, pero ¿acaso es imprescindible? ¿Es que no tenemos el código objeto...? Desde que en 1987 se diera el primer caso documentado de ataque de un virus informático en la Universidad de Delaware, mucho se ha dicho y escrito sobre el tema dentro de los foros más insospechados. A pesar de ello los virus siguen siendo grandes desconocidos para la gran mayoría, razón que sin duda contribuye no poco a que cada vez sea mayor el número de ellos en expansión. Conocer y entender la forma en que actúan, se reproducen y se comportan es la mejor forma de estar prevenido y saber cómo reaccionar; la solución de formatear el disco duro nada más enterarse de que dentro hay un virus es bastante extrema y, por lo demás, suele eliminar toda la información del disco duro... a excepción del propio virus. Por otra parte, ya no en el caso de un particular, sino en el de las empresas, es muy posible que no puedan permitirse esperar a que se saque la siguiente versión de un antivirus que elimine el que les afecta, y necesiten que alguien realice un estudio inmediato de su virus, identifique su forma de actuar, sus efectos, y se pueda poner a corregirlos cuanto antes. Esta serie de artículos tratará de dar cumplida visión del funcionamiento y técnicas de estos programas, pensando que el conocimiento y el entendimiento son dos armas básicas en la lucha contra cualquier amenaza. Hay quien opina que este tipo de información no debe hacerse pública para evitar que más gente se dedique a fabricar virus. Pero, ya que según los estudiosos la media de aparición de virus nuevos viene a ser de unos 5 o 6 cada día -por no contar las modificaciones y mutaciones sobre otros ya existentes-, es fácil llegar a la conclusión de que "los malos" ya tienen toda la información que necesitan. Normalmente las políticas de ocultación de información de este tipo con ánimo de evitar que se utilice con "malos fines" fracasan estrepitosamente al acabar siempre desinformados los que sufren los ataques y nunca quienes los perpetran, cosa que ayuda a que los ataques sean mucho más efectivos. Por eso aquí se tratará con virus reales, estudiando su comportamiento a través de su propio código. La naturaleza de estos programas obliga, eso sí, a suponer en el lector conocimientos mínimos de lenguaje ensamblador. Se explicarán construcciones
crípticas u operaciones poco evidentes por estar fuertemente ligadas a la arquitectura de la máquina, pero no el repertorio de instrucciones o los modos de direccionamiento. Para información sobre estos aspectos, referirse a la bibliografía. LOS COMIENZOS La paternidad de la idea de un programa capaz de reproducirse se le atribuye a John von Neumann por su artículo "Theory and Organization of Complicated Automata" del año 1949. Von Neumann estaba interesado en la creación de vidas artificiales electrónicamente, a las que daba el nombre de autómatas que, según él, podían reproducirse sin excesiva dificultad. También por aquel entonces, cuando empezaban a desarrollarse los primeros ordenadores, se vio la necesidad de llevar a la computadora a un estado inicial conocido, eliminando rastros de otros programas previamente cargados. Una solución muy ingeniosa consistía en implementar una instrucción que simplemente se copiaba a la siguiente posición de memoria y saltaba a ella para seguir ejecutándose. De esta forma todo el mapa de memoria se llenaba con un único valor conocido: el código correspondiente a la instrucción. Este fue otro ejemplo claro de código capaz de reproducirse. Seguramente basado en él, más adelante, en los años 60, cobró gran popularidad el juego "Core Wars", diseñado en los laboratorios Bell de AT&T: dos jugadores lanzaban simultáneamente sus programas que se reproducían en memoria hasta que ésta se agotaba. Ganaba aquel que fuera capaz de "conquistar" más memoria, para lo cual era perfectamente legal "matar" a las copias del adversario y "robarle" esa memoria. Aparte del méramente lúdico, también se trató de darle un uso algo más práctico a este tipo de programas, y así, a finales de los años 70, dos investigadores del Centro de Investigación Xerox de Palo Alto, California, idearon un programa que debía encargarse de las labores de mantenimiento y administración del complejo, al que dieron el nombre de "gusano". El programa "dormía" por el día y por la noche se propagaba por todos los ordenadores del Centro haciendo copias de seguridad y otras tareas de gestión. Todo esto en la teoría, porque en la práctica el gusano escapó de los ordenadores de prueba del laboratorio, se extendió por toda la red y paralizó todas las máquinas. Al intentar eliminarlo, seguía reapareciendo, así que no hubo más remedio que crear otro programa vermicida que fuera por todas las máquinas matando copias del gusano. LOS AÑOS 80 La palabra virus no se empezó a usar hasta que Fred Cohen, un estudiante graduado de la Universidad del Sur de California, lo utilizó en 1984 para su tesis sobre programas autoduplicadores. En ella daba la primera definición formal del término y hacía un estudio matemático de la expansión de este tipo de engendros, mediante el cual demostró que crear un programa detector de virus perfecto era lógicamente imposible (ver recuadro). Como se dijo antes, en 1987, el 22 de octubre, se da el primer ataque de virus del que se tiene noticia. En realidad era inofensivo y contenía la dirección y teléfono de los
autores: dos hermanos de Pakistán, estudiantes de la Universidad de Lahore, aunque, al parecer, hubo un estudiante que perdió su tesis debido a efectos secundarios del virus. También ese año se produce la primera infección masiva sobre ordenadores Macintosh: un consultor de Aldus Corporation se infectó con un disco de juegos y al realizar luego pruebas al programa Aldus Freehand contaminó el disco que luego distribuyó su empresa. El virus se limitaba a presentar en pantalla un "mensaje universal de paz" a todos los usuarios del Mac, firmado por Richard Brandow, editor de la revista MacMag, al llegar al 2 de Marzo de 1988 (fecha del aniversario de la aparicíon del Macintosh II) y se autodestruía inmediatamente. Pero los buenos deseos de paz pronto se transformaron en sentimientos menos encomiables. Así, tiempo después, un banco de la isla de Malta se vio afectado por un virus que sacaba el siguiente mensaje por pantalla: "DISK DESTROYER - A SOUVENIR OF MALTA / I have just DESTROYED the FAT on your Disk!! / However, I have kept a copy in RAM, / and I'm giving you / a last chance to restore your precious data./ WARNING: IF YOU RESET NOW ALL YOUR / DATA / WILL BE LOST FOREVER!! / Your data depends on a game of JACKPOT / CASINO DE MALTE JACKPOT/ +L+ +?+ +C+ / CREDITS: 5 / <
>" El virus borraba la FAT, una estructura básica de cualquier disco DOS que permite encontrar los datos, pero guardaba una copia en memoria. Después invitaba a jugar a las tragaperras: si salían tres "L", restauraba la FAT y no se perdían datos, pero si salían tres "?" o tres "C", destruía también la copia de RAM y se perdían todos los datos. Como era de esperar, el banco perdió la información en dos terceras partes de sus máquinas. Ningún detector de virus puede ser perfecto. En su tesis doctoral para la Universidad de California, Los Angeles, Fred Cohen demostraba, en 1983, que no hay ningún algoritmo general que pueda concluir con total fiabilidad -100%- si un programa es o no un virus. Para ello se valía de la siguiente demostración por reducción al absurdo: Supóngase que existe un algoritmo general A que, analizando cualquier programa P, devuelve "true" si y sólo si P es un virus. Entonces sería posible crear un programa, P, que hiciera lo siguiente: if ( A(P) = false ) then infecta el sistema if ( A(P) = true ) then no infectes nada
Es decir: P es un virus si A dice que no lo es, y no lo es si A dice que lo es. Por contradicción, ese algoritmo general A no existe.
HISTORIA LIGADA AL DOS
Aunque también se han dado casos de virus en otros sistemas operativos, la inmensa mayoría son propios del D.O.S. Esto no es difícil de entender si se tienen en cuenta los nulos mecanismos de protección de este entorno en comparación, por ejemplo, con UNIX en donde existen dueños y permisos de ficheros, o el sistema operativo limita el acceso a partes críticas del hardware y del propio sistema operativo. La creación de virus en estos sistemas que ofrecen ciertas protecciones no es, a pesar de todo, imposible, y desde febrero de este año han aparecido nuevas versiones de un virus para Linux, el Bliss, con características francamente interesantes : guarda su número de versión en las infecciones, y actualiza éstas si él mismo es una versión más moderna, o se auto-actualiza si se encuentra con un fichero infectado con una versión más reciente de sí mismo ; modifica las fuentes del kernel para poder hacerse root mediante llamadas poco convencionales a la función umask ; como a todo buen programa UNIX, se le puede llamar especificando opciones en la línea de comandos, alguna tan singular como "-disinfect_files_please" que, efectivamente, desinfecta el fichero que se le diga ; guarda logs de todas las infecciones en el /tmp, etc. Es un virus experimental, aunque, ciertamente, promete. TIPOS DE VIRUS Pese a que desde aquel no tan lejano 1987 el número de virus existente ha crecido ampliamente por encima del millar, todos ellos pueden ser englobados en tres categorías: virus de arranque, virus de ejecutables y, recientemente, virus de macros, interpretados por otras aplicaciones. Los virus de arranque toman el control antes de que se cargue el sistema operativo, y son por tanto independientes de él; se localizan en los sectores de arranque de los disquetes y en la Master Boot Record (Tabla de Particiones) de los discos duros. Serán el objeto de estudio del próximo artículo. Los virus de ejecutables modifican los ficheros .EXE o .COM para alojarse en ellos añadiéndose a su código a modo de mochila para ejecutarse cuando se llama al programa anfitrión. A diferencia de los de arranque, que siempre son residentes en memoria, estos pueden optar entre serlo o no, lo que influirá tanto en su velocidad de difusión como en la facilidad y forma de ser detectados. Por último, los virus de macros son consecuencia de la inclusión por parte de Microsoft de un lenguaje de programación, WordBasic, para crear macros que se interpretan en los documentos de Word versíon 6.x y superiores, así como en hojas de cálculo Excel. Han sido revolucionarios al ser los primeros virus que no infectan código, sino datos, y además independientemente de la plataforma en la que se encuentren. Todos los demás virus, al estar escritos en última instancia en código máquina de un procesador determinado, sólo funcionan en este tipo de máquinas. Estos en cambio funcionan lo mismo en un Word ejecutándose sobre plataformas Intel, Motorola o PowerPC. UN POCO DE VOCABULARIO Hasta ahora hemos hablado de virus sin dar una definición más o menos formal, confiando en esa idea más o menos intuitiva que tiene todo el mundo de lo que son. En
realidad la definición no dista mucho de la dada inicialmente por Cohen, aunque hay que modificarla ligeramente para dar cabida en ella a los virus de macros. Un virus de ordenador es un programa (en general, aunque no necesariamente, compilado) capaz de reproducirse infectando otros programas, a los que añade o sobrescribe su código -posiblemente encriptado o modificado- de modo que ese código sea, a su vez, capaz de reproducirse de nuevo infectando otros programas. La infección puede hacerse de forma indirecta: no es imprescindible modificar el código del programa que se va a infectar. Algunos virus, como el "Dir-II", alteran las entradas de directorio para que al ejecutar un fichero, primero se les llame a ellos. De esta forma el programa original no se toca, pero la entrada en el directorio sí. Al hablar de "programa" no debe pensarse sólo en ficheros ejecutables. En un sector de arranque también hay un programa; una macro contiene una serie de acciones que se ejecutan secuencialmente, y como tal, también es un programa. El mero hecho de reproducirse no implica que el programa sea un virus. Concretamente, este tipo de software que crea y expande copias de sí mismo pero sin unirse a otros programas ni modificarlos suele denominarse gusano. El caso más conocido de gusanos es, sin duda, el gusano de Internet, que Robert Morris Jr. soltó en la red ARPANET en la tarde del miércoles 2 de Noviembre de 1988. En poco más de 3 horas saturó ARPANET, entró en MILNET (la red del Departamento de Defensa americano) y se extendió por Internet, liberando copias de sí mismo de un extremo a otro del continente. Pasada la medianoche las universidades de California en Berkeley, San Diego, Stanford, Maryland, Pardue, el MIT, el Ames Laboratory de la NASA, Aerospace y el Laboratorio de Investigación Balística del ejército en Maryland estaban ya completamente colapsados con copias del gusano. Muchos centros se desconectaron de la red como única forma de evitar que sus ordenadores se paralizaran. El gusano no modificaba ni destruía nada de los ordenadores en que se colaba. Simplemente aprovechaba un agujero de seguridad en el sendmail -programa que envía y recibe el correo electrónico- que permitía ejecutar comandos como root desde ordenadores remotos, y otro en el finger, que tenía problemas de "buffer overrun", para transmitirse de un ordenador a otro. Era autónomo, y por tanto no era un virus. Tampoco el hecho de tener comportamientos no esperados ni deseados por parte del que ejecuta un programa es suficiente para catalogarlo como virus. Si no se reproduce se trata, más bien, de un "caballo de Troya", es decir, un programa que aparenta un comportamiento (normalmente atractivo para instar a los usuarios a que lo ejecuten) pero que después lleva a cabo otro tipo de tareas no deseadas o documentadas. Pero, dentro de los propios virus, hay un extenso vocabulario -normalmente sin traducción, por lo menos generalmente aceptada, al español- que permite catalogarlos y diferenciar distintos comportamientos. Se dice que un virus es polimórfico cuando es capaz de modificar las copias que hace de sí mismo con el objeto de hacer más difícil su identificación por parte de programas antivirus. Esto se consigue, por ejemplo, encriptando con semillas aleatorias dichas copias, usando técnicas distintas de encriptación en cada infección, insertando instrucciones que no tienen efecto sobre el propósito del fragmento de código modificado, o reemplazando instrucciones por otras con el mismo resultado, pero distinto código máquina (para poner el registro AX a cero son equivalentes: "mov ax,0", "sub ax,ax", "xor ax,ax" o "mov ax,FFFF; inc ax".
Un virus se dice que utiliza técnicas stealth cuando incorpora rutinas para tratar de ocultar o disimular sus "huellas". Muchos virus de arranque redirigen las llamadas a la interrupción 13h de la BIOS (encargada del control de discos duros y disqueteras) para que si algún programa de diagnóstico trata de inspeccionar el sector de arranque el virus lo detecte y,en lugar de mostrárselo, redirija la llamada para enseñarle otro sector en el que guarda una copia del sector de arranque original no infectado. De esta forma y mientras el virus esté activo en memoria, la ilusión será que el sector de arranque se encuentra intacto. Para los virus que infectan ejecutables hay técnicas parecidas: normalmente se puede detectar la infección mirando si el programa ha "crecido". Pero si el virus es lo suficientemente inteligente puede interceptar las llamadas al sistema y devolver el tamaño original del fichero y así, al hacer un "dir", parecerá que su tamaño se mantiene como siempre. Todas estas técnicas exigen que el programa se encuentre continuamente residente en memoria; no basta con que se active cada vez que ejecutamos un programa infectado. Un armoured virus intenta hacer lo más difícil posible el desensamblado y estudio del virus. Utiliza las mismas técnicas que otros programas comerciales que funcionan bien en condiciones normales, pero al intentar inspeccionar su código se "cuelgan". Un companion virus no modifica los ejecutables. En su lugar crea otro programa que se ejecuta antes que el original, y a continuación llama al que quería el usuario. En PCs ejecutándose bajo DOS esto suele conseguirse creando un fichero con el mismo nombre que el que se quiere suplantar, pero con extensión COM en vez de EXE. De esta forma, si el usuario quiere que se ejecute PROGRAMA.EXE y lo llama escribiendo simplemente "PROGRAMA", pero el virus ha creado ya un "PROGRAMA.COM" será éste el que se ejecute y no el .EXE. Un cavity virus no añade su código al programa que infecta, sino que lo sobrescribe. Para que el programa infectado siga funcionando es preciso que el virus conozca previamente su estructura interna para que tras la sobrescritura el programa siga funcionando. Evidentemente esto restringe la infección a ficheros muy concretos, especialmente el COMMAND.COM. Uno de los primeros virus, el Lehigh, usaba esta técnica. Por último, un tunnelling virus es uno particularmente ingenioso y complicado: examina las rutinas de interrupción de la BIOS y el DOS, y va "subiendo" hasta encontrar las rutinas originales. De esta forma, si se ha instalado un antivirus que "espíe" las llamadas a estos servicios, no detectará las acciones de estos virus pues llaman directamente a las rutinas originales sin pasar por las interpuestas por el antivirus. TUTORIAL DEBUG A la hora de analizar y eliminar virus será necesario valerse de algunas herramientas tales como desensambladores o editores hexadecimales. Para la casi totalidad de los casos bastará con un pequeño programa que acompaña al DOS y que, no obstante su utilidad, es un gran desconocido para la mayoría de los usuarios: el DEBUG.
Se trata de una utilidad bastante poco amigable, cierto, sin menús y -sólo en versiones recientes- con ayuda mínima, pero una vez aprendidos unos cuantos comandos es extremadamente potente. Hay quien lo considera como el mejor programa que ha hecho Microsoft. Debug, invocado sin parámetros, se carga y pasa a mostrar un lacónico guión, a la espera de órdenes del usuario. Todas -a excepción de las relativas a operaciones con memoria expandida, que no se verán aquí- constan de una única letra seguida o no de parámetros. Si se va a desensamblar un programa, se le puede indicar a debug que lo cargue también tras cargarse él escribiendo: debug nombre-del-programa argumentos-del-programa
El programa no se ejecuta; simplemente se carga en memoria. Al igual que de la manera anterior, aparece un guión esperando alguna de las órdenes siguientes: A: ASSEMBLE Sin parámetros ensambla las instrucciones que se introduzcan, guardándolas en la dirección siguiente a la que se llegó en el último "a". También se le puede decir qué zona se desea ver con a pudiendo ser la dirección absoluta (a segmento:desplazamiento) o relativa al segmento de código actual (a desplazamiento). D: DUMP Muestra el contenido de una zona de memoria en hexadecimal y en ASCII. Sin parámetros muestra los primeros 128 bytes a partir de la posición a la que se llegó en el último "d". Si se le da un rango, mostrará ese rango. E DIRECCION: EDIT Permite editar, byte por byte, una zona de memoria. Muestra -en hexadecimal- el byte de esa posición y permite escribir otro valor para cambiarlo. Pulsando espacio pasa al byte siguiente, dejando como estaba el anterior si no se ha cambiado, o guardando los cambios si sí se ha hecho. Para terminar la edición se pulsa Enter. F: FILL Llena una zona de memoria con un valor determinado. Como al terminar un programa la zona de memoria en que se residía no se borra (poniéndola a cero, por ejemplo), a menudo es útil para distinguir entre lo que son datos del programa actual y lo que es basura del anterior. Ej: -f 100 ffff 0
Llena de ceros lo que hay desde la posición 100 (hexadecimal) hasta el final del segmento. G: GO Sin parámetros, empieza a ejecutar desde la posición cs:ip hasta que se acabe el programa. Si la orden es "g ", la ejecución empieza en cs:ip y termina (debug pone un break point) justo antes de ejecutar la instrucción que se encuentra en . H NUM1 NUM2: HEX Muestra por pantalla el resultado (en hexadecimal) de las operaciones num1+num2 y num1-num2. L: LOAD La sintaxis completa es l [direccion] [unidad] [sector] [nsectores]. Si se le dan todos los parámetros lee de la unidad dada, empezando en el sector indicado, tantos sectores como determine "nsectores" y los guarda en la posición de memoria especificada por "direccion". Poniendo sólo l direccion intenta cargar el fichero definido con la orden n (ver abajo) en esa posición. M RANGO DIRECCION: MOVE Mueve la zona de memoria delimitada por "rango" a la dirección "direccion". N NOMBRE: NAME Da un nombre de fichero sobre el que actúan l y w. P: STEP Trace puede ser incómodo si no se quiere depurar el código de las rutinas de interrupción (cosa en general poco útil, ... salvo que estemos tratando con virus) o si ya se sabe el código que hay en las subrutinas y tan sólo interesa seguir avanzando sin entrar en ellas. En estos casos se usa p. Q: QUIT Salir de debug y volver al DOS. R: REGISTERS
Sin parámetros, muestra el contenido de los registros de la CPU, así como la próxima instrucción a ejecutar. r registro muestra el contenido del registro especificado y cambia el prompt de "-" a ":" invitando a que se cambie su valor. Pulsando Enter sin más lo deja como estaba. S: SEARCH Busca una secuencia de bytes o una cadena en el rango especificado, mostrando la dirección en que aparece en pantalla. s 100 500 cd 13 Busca todas las llamadas a la interrupción 13h (código máquina cdh 13h). s 100:0 ffff "virus" muestra las zonas de memoria del segmento 100h en que aparece la palabra "virus". T: TRACE Ejecuta la instrucción apuntada por cs:ip y vuelve a debug. Permite ejecutar paso a paso un programa -o un virus- y observar en cada instrucción el efecto producido. Si haciendo "trace" se llega a una subrutina (CALL) o a una interrupción (INT) la siguiente instrucción que se ejecutará será la primera de la subrutina o la primera de la rutina de atención de la interrupción correspondiente. U : UNASSEMBLE Desensambla una zona de memoria. Si no se le dan parámetros empieza a hacerlo en la dirección apuntada por cs:ip. También se le puede decir qué zona se quiere ver con u pudiendo ser la dirección absoluta (u segmento:desplazamiento) o relativa al segmento de código actual (u desplazamiento). Si se da un rango de direcciones desensamblará esa zona: u 1000 2000 desensambla el código que haya desde cs:1000 a cs:2000. Todos los números son tratados como hexadecimales, así que u 1000 empieza a desensamblar desde la posición 4096 (decimal) del segmento de código. W: WRITE La sintaxis completa es w [direccion] [unidad] [sector] [nsectores] Con todos los parámetros escribe en la unidad y sectores dados el contenido de la dirección de memoria. Pasándole sólo la dirección escribe en el fichero definido con n (ver arriba) tantos bytes como determinen el par de registros BX:CX a partir de la posición de memoria dada. ?: AYUDA?
En versiones recientes de MS-DOS (por lo menos a partir de la 5) muestra una miniayuda. Existen algunas órdenes más para tratar con memoria expandida y otras operaciones que no serán de especial relevancia para el tema que nos ocupa. Para asentar ideas y de paso aprender algunos pasos útiles a los que se hará referencia varias veces a lo largo de esta entrega se verán, para acabar, unos ejemplos. Guardar una copia del sector de arranque del disco duro en el fichero a:sector.arr C:\> debug l 100 2 0 1 na:sector.arr w100 q
Guardar una copia de la tabla de particiones del primer disco duro. Esto requiere llamar a rutinas de la BIOS puesto que ese sector está fuera de la partición lógica DOS (esto se explicará con detalle en la próxima entrega): C:\> debug a XXXX:100 mov ax,0201 XXXX:103 mov bx,7c00 XXXX:106 mov cx,1 XXXX:109 mov dx,80 XXXX:10C int 13 XXXX:10E int 20 XXXX:110 g El programa ha terminado de forma normal r cx CX XXXX :200 n a:partic.tbl w 7c00 Escribiendo 00200 bytes q
Como ejercicio, se puede echar un vistazo a la tabla de particiones (usando "d"), que tras ejecutar el mini-programa anterior queda en ES:7c00, y luego desensamblarla ("u") para ver lo que hace. Servirá de preparación para el mes que viene, en que se estudiará en detalle.
•
REFERENCIAS El número de Febrero de 1987 de "Computers & Security", Vol.6, contiene el artículo de Fred Cohen en el que define el concepto de virus de ordenadores:
"Computer Viruses: Theory and Experiments", pags. 22-35. •
Los escritos de Fred Cohen pueden pedirse a: ASP Press, PO Box 81270, Pittsburgh, PA 15217, USA.
•
Para una descripción histórica de la evolución de los virus se pueden consultar las siguientes direcciones: Dr.Solomon's History of PC Viruses: http://dbweb.agora.stm.it/webforum/virus/solomhis.htm Robert Sladés Virus History: http://dbweb.agora.stm.it/webforum/virus/sladehis.htm
•
Un excelente libro sobre todo tipo de delitos informáticos, exhaustivamente documentado y traducido al español es: "Los piratas del chip", de Bryan Clough y Paul Mungo. Ediciones B.
•
Como referencia para la programación en ensamblador, desde el número 1 de Programación Actual se viene ofreciendo un curso sobre el tema.