Analisis de virus: Avispa En este numero vemos un virus argentino que causó problemas a mucha gente, el Avispa. El virus avispa infecta solamente archivos .exe. En el momento de ejecutarse, si no se encontraba residente en memoria, intenta infectar los archivos c:\dos\xcopy.exe, c:\dos\mem.exe, c:\dos\setver.exe y c:\dos\emm386.exe, en el caso de que no estén previamente infectados. Obviamente lo hace para maximizar sus posibilidades de reproducción, ya que esos archivos suelen estar en el autoexec.bat o son muy usados. El virus queda residente en memoria como si fuera un residente común, no hace ningun esfuerzo para ocultarse en memoria, excepto que se copia al segmento del PSP del programa, para ocupar menos memoria una vez residente. Una vez residente va a infectar cada programa que empieze con los bytes MZ (identificador de .EXE) que se intente ejecutar. Está encriptado, y cada vez se encripta con una clave distinta, si bien siempre usa el mismo algoritmo. El virus funciona solamente en 386 y superiores, ya que usa instrucciones que sólo se encuentran en estos procesadores. Tiene una rutina de daño muy interesante, de un tipo muy poco usado. En vez de modificar o destruir información en el disco, lo que hace es modificar el retorno de la interrupción 13h. Cuando se hace un pedido de leer uno o más sectores a partir del cilindro 10 en adelante con la interrupción 13h, y en ese momento el word más bajo del system timer está en 0, el virus llena los primeros 512 bytes del buffer de datos con el texto '$$ Virus AVISPA $$ Republica Argentina$$ Elijah Baley $$ Noviembre 10 de 1993 $$ This program is not an old virus variant, and it was written in Argentina by Elijah Baley. $$=64446$$', más los datos que se encuentren a continuación del virus en memoria, hasta llenar 512 bytes. Por lo tanto, el virus no afecta a los datos del disco, pero afecta a los programas que leen datos del disco. En el caso de que lo que se lea con error sea un programa, probablemente se cuelgue la máquina. El word bajo del system timer está en cero cada poco más de media hora, así que en el funcionamiento normal de una máquina el efecto va a ser muy notable. El virus contiene los siguientes textos, encriptados: c:\dos\xcopy.exe c:\dos\mem.exe c:\dos\setver.exe c:\dos\emm386.exe __ Virus Avispa - Buenos Aires - Noviembre 1993 __ $$ Virus AVISPA $$ Republica Argentina$$ Elijah Baley $$ Noviembre 10 de 1993 $$ This program is not an old virus variant, and it was written in Argentina by Elijah Baley. $$=64446$$ Podemos agregar que Elijah Baley es un personaje de la novela 'Bóvedas de Acero', de Isaac Asimov, y de las novelas que continuan su saga. Notemos tambien que fue escrito en noviembre de 1993, y que en muy pocos meses se convirtió en epidemia. Estamos viviendo en Argentina una época donde, al contrario de lo normal, los virus que más infectan son los nuevos y no los viejos, y son originados en Argentina. Funcionamiento El Avispa, como ya dijimos, está encriptado, siempre con una clave distinta. Obviamente, lo primero que hace antes de nada es desencriptar su código. Para esto, hace un simple XOR de cada word del código con un valor. El loop de desencriptado es muy interesante porque en lugar de poner las constantes en forma directa, carga primero un valor en el registro y luego le suma otro valor para obtener el buscado. Esto es obviamente para dificultar su análisis automático con scaneadores heurísticos, por ejemplo. Podemos ver cómo lo hace con este pedazo de código, donde inicializa las constantes usadas por el desencriptor. comienzo: mov bx,11Eh mov bh,bh add bl,1Ch ; bx = 13Ah mov ch,ch Acá vemos que BX primero vale 11Eh y luego se le suma 1Ch para llegar al valor 13Ah, que es el comienzo del código a desencriptar. Las instrucciones mov bh, bh y mov ch, ch son instrucciones que no hacen nada, y son insertadas al azar por el virus en el desencriptor, para que sea difícil encontrar el virus con un string constante de scaneo. El siguiente código es el desencriptor en sí, que sigue con el mismo criterio. Como veremos más adelante, el desencriptor es variable en más que en las instrucciones que no hacen nada insertadas. desencriptar: mov ax,cs:[bx] ; leer en ax para desencriptar mov ch,ch mov cx,13Bh mov ch,ch sub cl,29h ; cx = 164h (clave) mov dh,dh xor ax,cx ; ax es desencriptado nop mov dh,dh mov cs:[bx],ax ; poner ax donde estaba mov dh,dh add bx,2 ; incrementar en 2 bx mov al,al mov ax,0FADBh mov al,al add ax,0DF1h ; ax = 8CCh mov al,al nop cmp bx,ax ; bx es = 8CCh? (largo del código) mov bl,bl jc desencriptar ; Si no es asi, sigue desencriptando El desencriptor es mucho más largo de lo que debería ser justamente por estas consideraciones polimórficas. El virus es casi polimórfico, porque hay pocas variantes de desencriptores que puede generar. El método de desencripción es extremadamente sencillo, hasta podríamos decir que es clásico, un XOR con un valor aleatorio. Cuando termina de desencriptar, salta por encima de una zona de variables y empieza el virus en sí. Primero intenta verificar si está residente en memoria, utilizando un servicio de la interrupción 21h redefinido por el virus, el 4BFFh. Si retorna 4BFEh significa que el virus estaba residente en memoria. Si es así, simplemente restaura el stack definido por el header original del programa y salta (mediante un ret far) al programa original. Si no está residente en memoria, se copia al segmento del PSP (al que apunta el DS al cargarse el programa), y sigue ejecutándose en esa posición mediante un ret far hacia el nuevo segmento donde reside. Una vez copiado libera toda la memoria excepto la necesaria para si mismo. Guarda el valor actual del vector de la interrupción 21h y de la 13h en variables, y los redefine a su propio código, poniendo las rutinas de auto- detección e infección en la 21h y la de daño en la interrupción 13h. Una vez que hace esto, busca en el segmento del environment el nombre del programa huesped del virus, para ejecutarlo. Si no lo encuentra, simplemente queda residente y no lo ejecuta. Una vez encontrado el nombre, llama a la interrupción 21h (mediante el vector que tenía salvado, para que no intente infectar al programa nuevamente) y utiliza la función 4B00h para ejecutarlo. Notemos que carga al programa nuevamente, por lo cual en discos lentos o programas largos puede notarse una demora en la carga del primer programa infectado. Luego de ejecutarlo, infecta los programas pre-definidos para infectar: 'c:\dos\xcopy.exe', 'c:\dos\mem.exe', y 'c:\dos\setver.exe'. Luego libera la memoria que antes había reservado, y queda residente usando la función 31h de la interrupción 21h. Notemos que esta estrategia es muy interesante, ya que el virus, cuando ejecuta el programa, ya tiene el control de la interrupción 21h, por lo que infectaría a cualquier programa que se ejecute mientras dure la ejecución del huésped, y recién ejecuta a sus víctimas pre- seleccionadas cuando este programa termina, con lo cual hay menos demora en la carga del programa. Tambien notemos que intenta infectar a esos programas del DOS en el caso de que no se encontrara ya residente en memoria, porque considera que si estaba residente esos programas ya están infectados. Interrupción 21h El handler para esta interrupción es el que se ocupa de infectar. Primero verifica que la función llamada sea la 4BFFh, y si es así, devuelve 4BFEh en AX, y vuelve de la interrupción. Con esto se auto-detecta en memoria. Si la función llamada es la 4B00h se prepara para infectar. Si no es ninguna de las dos, sigue con la interrupción 21h del DOS. Para infectar, guarda el puntero hacia el nombre del programa que se intenta ejecutar en una variable, y llama a la subrutina de infección. Luego vuelve a la interrupción 21h normal. La rutina de infección llama a otra rutina que chequea si el nombre del programa a ejecutar termina en AN, LD u OT. Obviamtente busca a los programas scAN.exe, f-prOT.exe y alguno que termina en LD y que no se me ocurre en este momento. Si descubre que se trata de uno de esos programas, devuelve 1 en AH, si no es así, devuelve 0. La rutina de infección verifica si devolvió 1, y en ese caso no intenta infectar el programa. En el caso de que decida infectarlo, abre el archivo, y lee sus primeros 127 bytes. Verifica si empieza con MZ, el identificador de .EXE, y si no es así, sale sin infectar. Si es un .EXE lo que leyó es su header. Verifica que el CS y el IP inicial, definidos en el header, sean distintos a 0, si por lo menos uno de ellos es distinto a 0 sigue infectandolo. Abre el archivo, y si hay algún error en la apertura (quizá debido a que ese archivo no existe), no intenta seguir infectando. Si pudo abrirlo verifica que el archivo no sea más largo a lo declarado en el header del .EXE. Si es más largo considera que tiene overlays o algo así, y no lo infecta. Si luego de todas estas pruebas lee los últimos 52 bytes del archivo, para la última prueba, verificar si el programa estaba infectado previamente. Estos últimos 52 bytes, en el caso de un programa infectado, contienen el texto encriptado '__ Virus Avispa - Buenos Aires - Noviembre 1993 __' precedido por la clave con la cual está encriptado. Se desencripta con un XOR de cada word del texto con la clave. Este método puede usarse para escribir un programa para detectar al virus, con un 100% de seguridad: se leen los últimos 52 bytes del archivo .EXE a verificar, se toma el primer word del mismo, y se hace un XOR de ese valor con cada uno de los words restantes. Si el texto desencriptado es el que ya mencionamos, el archivo está infectado. El virus, de todas formas, no hace exactamente esto. Lo que hace es desencriptar cada word y sumarlo en DX, y luego comparar si DX termina valiendo 7DDAh. Si es así, considera que el programa está previamente infectado. En definitiva, lo que hace es calcular un checksum del mensaje, en vez de compararlo con el texto real. En cuestión de código no se ahorra nada haciéndolo, hacerlo así es más largo que comparar con una instrucción de comparación del procesador. Si el programa no está infectado, procede a hacerlo. Primero toma la interrupción 24h y la reemplaza por una que marca en una variable que fue llamada y luego vuelve, con lo cual puede saber si hubo error chequeando esa variable, y no alerta al usuario de posibles errores. Luego lee los atributos del archivo, y los guarda en una variable. Borra todo atributo del archivo, con lo cual puede escribir sobre él sin problemas. Vuelve a poner la interrupción 24h normal, y verificar que no hubieron errores durante su ejecución, mediante la variable que modifica el handler que instaló antes. Luego abre el archivo, y guarda la fecha y hora del mismo en una variable. Va al final del archivo, y empieza a rellenarlo de bytes 0 hasta que su largo queda divisible por 16. Luego procede a modificar el header del .EXE que tenía leído. Suma 4 al número de parrafos del .EXE, guarda el IP, CS, SS y SP originales en variables y los modifica por los adecuados para ejecutar el virus. Luego genera una zona de memoria para trabajo, reservando 90h párrafos de memoria y copiando el virus a esa zona. Eso lo usa de área de trabajo para encriptar el virus y generar el desenctriptor. Llama una subrutina que genera el desencriptor y luego el virus encriptado. Toma el byte más bajo del reloj de la máquina y se lo resta al offset del origen del código a desencriptar. Luego escribe en el desencriptor las operaciones necesarias como para volver a obtener el mismo valor. Genera una clave al azar a partir del reloj de la máquina, también dividiéndolo en dos operaciones, y generando el código para desencriptar con esa clave. Luego, al azar, genera el resto del desencriptor a partir de un número bastante corto de posibilidades. Básicamente, invierte algunas operaciones de orden y agrega algunos nops para rellenar en ciertas partes. Genera la condición de salida del loop del desencripor, comparando si llegó al offset 2252, o sea, a los 1996 bytes que quiere desencriptar, también con un número partido en dos partes aleatorias. Luego rellena los espacios vacíos que fue dejando de código con operaciones que no hacen nada al azar. A continuación encripta el virus y le agrega al final el texto que nombramos antes, encriptado. Luego escribe el virus al final del archivo, sobreescribe el header del .EXE por el modificado, recupera la hora y día del archivo originales, recupera los atributos, y libera la memoria que había reservado para trabajar. El archivo creció 2048 bytes más lo necesario como para que su largo quede divisible por 16. Después de infectar, vuelve a la interrupción 21h normal. Rutina de daño La rutina que reemplaza a la interrupción 13h verifica si está llamándose a la función 02h, leer sectores. Si no es así, sigue con la interrupción 21h normal. Luego compara si el cilindro a leer es superior al 10. Si es así, se fija en el word más bajo del reloj del sistema. Si está en cero, llama a la interrupción 13h original con los parámetros con que la llamaron, y sobreescribe los primeros 512 bytes del buffer con el texto que mencionamos antes. Luego vuelve de la interrupción. Si no estaba en cero, continúa con la interrupción normal. Conclusiones Este virus es sencillo, tiene un método muy sencillo de polimorfismo, aunque es extremadamente fácil de detectar algorítmicamente (usando el método que ya describí). Es interesante el daño, ya que causa molestia, pero no tanta pérdida de información, por lo menos si la causa no es directamente sino indirectamente, por ejemplo si un programa copia de un lugar a otro un archivo, puede darse la casualidad que la copia destino quede destruida por el virus. Lo que no se sabe, como siempre, es cómo llegó a difundirse tanto por el país. Fernando Bonsembiante es jefe de redacción de Virus Report y está estudiando los virus informáticos dese hace varios años. Tambien es miembro de la comisión directiva del Círculo Argentino de Ciencia Ficción, (CACyF) y participa como columnista de varias revistas sobre informática. También es asesor en seguridad informática y virus en varias empresas. Puede ser contactado por Fido en 4:901/303 o en Internet en ubik@ubik.to