Analisis de virus: Avispa
Por Fernando Bonsembiante
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