Análisis de virus: DIR II


Uno de los virus más avanzados e ingeniosos de todos los que existen es 
el DIR II. Es quizá el único en usar un método de infección que no 
modifica ni los archivos ni los sectores de arranque.

Los autores de virus han ideado ingeniosas técnicas para que sus 
programas pasen inadvertidos y puedan lograr una mayor supervivencia en 
un medio que se les presenta cada día más hostil. El estudio del DIR II 
nos llevó a explorar uno de los caminos abiertos por estos extraños 
programadores. En ese viaje nos hemos encontrado con un trabajo audaz en 
donde se han puesto en juego conocimientos poco comunes y un afán de 
perfeccionamiento destinado a producir un código compacto en el que no 
se encuentra una sola instrucción de más.

El DIR II es uno de esos virus que a pesar de haber dejado de funcionar 
con las nuevas versiones del DOS sigue siendo interesante por la forma 
en que está hecho. La estrategia central de su funcionamiento es 
intervenir el device driver que utiliza el DOS para realizar las 
funciones de tratamiento de archivos en disco. El autor hace alarde de 
conocimientos sumamente especializados sobre las estructuras de datos 
internos del DOS: List of Lists, Device Parameter Block, Bios Parameter 
Block, Memory Control Block, Device Driver Header, Device Driver Request 
y otros que maneja con impecable sencillez.

Es quizá el máximo exponente de lo que podríamos llamar 'la generación 
búlgara' de virus, ya que fue originado en ese país en la época de 
máxima actividad de sus creadores de virus. Fue creado por dos alumnos 
de la 'Mathematical High School' de la ciudad de Varna, y se dice que 
usan el contador de infecciones del virus para hacer estudios 
matemáticos de la distribución de las infecciones. Sería interesante 
conocer sus resultados.

El código comienza redefiniendo el fondo del stack en el offset 600h y 
reservando los últimos 8 bytes para guardar, más tarde, los vectores 
originales de las interrupciones 21h y 13h.

Para descubrir el punto de entrada a la INT 21h el DIR II usa el vector 
de la INT 30h. Este "vector" no es en realidad un vector ya que si 
examinamos la dirección 0000:00C0 lo que vamos a encontrar es una 
instrucción de salto y no un puntero. Este JMP FAR viene de la versión 1 
del DOS, se trata de otro vestigio mantenido por razones de 
compatibilidad con el CP/M.

El DIR II no usa directamente la dirección a la que apunta el JMP FAR 
sino que toma como vector de la INT 21h el puntero que se encuentra 21h 
bytes más adelante del punto al que dirige el salto. Esta es una de las 
causas que hacen al DIR II incompatible con las versiones del DOS 
superiores a la 4 ya que desde la versión 5.0 ahí no hay ninguna 
dirección de entrada a la INT 21h.

A pesar de esta falta de compatibilidad y de otras que veremos más abajo 
no podemos decir que el autor del virus se haya despreocupado de este 
importante detalle. Al contrario, el virus todo el tiempo necesita 
acceder a tablas internas del DOS y lo hace con sumo cuidado porque el 
formato de esas tablas depende de la versión del DOS. Así asegura la 
compatibilidad con las versiones 4 y anteriores aunque, por supuesto, no 
pueda lograr lo mismo con las posteriores.

Luego de liberar la memoria que queda más alla del offset 600h del 
segmento actual, llama a la función 52h de la INT 21h para obtener el 
puntero a la "Lista de Listas". Esta es un estructura de datos 
complicada que posee abundante información, sobre todo punteros a otras 
estructuras de datos internos. De la Lista de Listas el virus obtiene el 
segmento del primer bloque de memoria MCB del DOS y el puntero al primer 
Drive Parameter Block (DPB). Para llamar a la función 52h, el virus usa 
el vector que obtuvo anteriormente como dijimos más arriba. Es 
justamente este hecho el que nos confirmó que al maniobrar con la 
"falsa" INT 30h lo que el virus hacía era buscar la entrada a la INT 
21h.

Las DPB son tablas cuyo formato depende de la versión del DOS. La 
información que contienen describe las características físicas del 
dispositivo al que pertenecen como por ejemplo el número de drive, la 
cantidad de bytes por sector, el número de FATs, el de entradas en el 
directorio raíz, etc. Estas tablas están encadenadas por un puntero que 
señala a la tabla siguiente. Mediante ese puntero el virus empieza a 
recorrer las diferentes DPB. En cada caso el virus examina el campo de 
la DPB donde se encuentra la dirección del header del device driver 
correspondiente. Lo que busca es el header del disco de booteo.

La manera en que el virus determina si el header corresponde o no al 
disco de arranque es otra de las razones que atentaron contra la 
compatibilidad del virus con las versiones actuales del DOS: busca un 
header cuyo segmento sea el 70h.

Si no encuentra un driver con estas características, da por terminado el 
programa con una llamada a la INT 20h.

Si lo encuentra, reemplaza la dirección del header original por una que 
apunta a un header propio. Además de esto, enciende un flag de la DBP 
que indica que el driver debe ser reinicializado. Ya volveremos sobre 
este detalle más abajo.

Después de reemplazar el header el virus usa el puntero al primer MCB 
para ver si el segmento donde está el virus es el segundo alocado por 
DOS. En DOS 3.1+ el primer MCB es el segmento de datos del DOS. En DOS 
4+ este segmento está dividido en varios Subsegments Control Blocks.

Si encuentra que el virus está en el segundo bloque de memoria, entonces 
modifica la longitud del primer MCB para que el bloque del virus quede 
incluido dentro del segmento de datos del DOS.

En cualquier caso, identifica al MCB del programa con el valor 0008 en 
la word de offset 1 del MCB. En este campo del MCB normalmente va el 
párrafo del dueño (owner) del MCB. La convención es que el owner 0008 es 
DOS.

Luego de ocultarse en RAM de esta manera, vuelve sobre el header del 
driver de disco que había intervenido. El header de un device driver 
contiene las direcciones de las dos rutinas que el DOS utiliza para 
comunicarse con el dispositivo: Strategy e Interrupt. El virus copia 
estas direcciones en su código completando una rutina con dos CALL FAR 
seguidos, el primero dirigido a la rutina de estrategia y el otro al de 
la rutina de interrupción. El DOS siempre llama a esas dos rutinas una a 
continuación de la otra.

Luego, el virus recorre el segmento del device driver en busca de un 
CALL FAR indirecto seguido de un RET 0002. El virus está seguro de 
encontrar este código y no se preocupa de controlar el fin de segmento. 
Salva el offset de la variable donde está la dirección del CALL FAR para 
poder llamar a esa rutina más tarde. Esta dirección de salto es la que 
toma como vector para la INT 13h. Recordemos que hasta ahora solamente 
había determinado el vector de la INT 21h. Sin embargo, en el caso de la 
INT 13h la determinación del vector es provisoria porque la que toma del 
código del driver la va a reemplazar por otra en el caso de encontrar 
una ROM de disco rígido.

Para ver si hay alguna ROM de disco rígido, el virus recorre la memoria 
a partir del segmento C000h. Recordemos que una ROM se identifica con la 
firma AA55H y que su longitud se calcula multiplicando por 512 el byte 
que sigue a esta firma.

En caso de encontrar una ROM, el virus la recorre en busca de una 
instrucción mov word ptr [004Ch],????. Esto es interesante porque 004Ch 
es justamente el offset donde se encuentra el vector de la INT 13h en la 
tabla de interrupciones. Así el virus puede detectar el lugar en donde 
la ROM del disco está modificando el vector de la INT 13h.

La búsqueda de una ROM de disco continúa mientras el segmento examinado 
sea inferior al F000h, en donde ya no puede haber ninguna ROM de 
controladora.

En caso de que la búsqueda haya arrojado un resultado positivo, modifica 
el vector de la INT 13h que había tomado tentativamente del driver 
original y lo reemplaza poniendo como segmento el de la ROM y como 
offset el valor movido a la dirección 0000:004Ch.

Llegado este punto, el virus ya se ha escondido en la zona de datos del 
DOS, ha logrado establecer los puntos de entrada a las INT 21h y 13h 
originales y se ha "colgado" del device driver de disco desde donde está 
en condiciones inmejorables para autoreplicarse.

Uno de los preparativos finales consiste en liberar la memoria del 
environment, llamando desde luego a la función 49h a través de la 
entrada original a la INT 21h. Después recorre el environment que acaba 
de liberar en busca de la especificación del archivo al cual está 
reemplazando para intentar ejecutarlo. Finalmente termina llamando a la 
función 4B00h del DOS, como siempre a través del vector de la INT 21h 
que supo conseguir al principio. A la vuelta lee el Error Level del 
programa ejecutado (función 4Dh) y lo devuelve con la función 4Ch que 
termina el programa.

Otro detalle a tener en cuenta, es que antes de ejecutar el programa 
cuyo nombre tomó del environment, salva ese nombre con su path completo 
y abre un archivo con nombre "C:",0FFh. A partir de este momento, el 
virus va a seguir actuando desde la rutina Strategy del device driver 
donde está colgado.

La estrategia del driver

Los llamados Instalable Device Drivers (IDD) son piezas de software cuya 
función es la de comandar a bajo nivel los dispositivos que controlan y 
brindar una interfaz standard para que el DOS la pueda utilizar desde un 
nivel más alto. Cada vez que DOS llama a un device driver lo hace 
pasándole en ES:BX un puntero a una estructura de datos con formato 
preestablecido. Esa estructura o packet tiene un número de comando con 
el que DOS indica al driver que función quiere que ejecute. La forma de 
llamar es siempre la misma: primero se llama a la rutina de estrategia y 
a continuación a la de interrupción. Las direcciones de estas dos 
rutinas están en el header de device driver. Como ya hemos visto, el DIR 
II cambia el header original del device driver del disco de arranque y 
lo reemplaza por uno propio. Una diferencia en la forma en que 
normalmente se instrumentan las rutinas de estrategia y de interrupción 
es que el DIR II hace todo el trabajo desde la de estrategia, mientras 
que normalmente es la rutina de interrupción la encargada de hacer el 
trabajo pesado. Esto es así por la intención que tiene el virus de 
anticiparse al funcionamiento del driver original interviniendo de ese 
modo el funcionamiento normal del DOS.

De la lista completa de comandos que el driver tiene que saber ejecutar, 
el virus elige solamente cuatro: 2, 4, 8 y 9. Los tres últimos 
corresponden respectivamente a: leer del dispositivo (input), escribir 
en el dispositivo (output) y escribir y verificar (output with verify). 
Sus nombres son suficientemente explicativos: son los comandos que 
utiliza el DOS cuando quiere lee o escribir un número de clusters. El 
comando número 2 se llama "Build BPB" y sirve para solicitar al driver 
que reconstruya la Bios Parameter Block. Esta tabla contiene datos que 
describen físicamente al dispositivo: número de bytes por sector, número 
de sectores por cluster, número de sectores reservados al comienzo del 
disco, número de FATs, número de entradas del directorio raíz, número 
total de sectores, número de sectores por FAT, número de sectores 
ocultos y otros datos por el estilo. En el caso de los discos rígidos, 
este comando solamente es invocado una vez, ya que los datos que 
contiene no pueden cambiar mientras la computadora está encendida. Sin 
embargo, en los diskettes, el DOS pide que se construya una nueva BPB 
cada vez que cree que ha podido producirse un cambio de discos.

Justamente lo primero que hace el virus cuando cambia la dirección del 
header del driver del disco de arranque es encender el flag de la DPB 
que indica que la BPB debe ser reconstruída. Así se asegura que el 
comando 2 sea llamado al menos una vez a partir del momento en que el 
virus entró en funcionamiento.

Cuando el virus intercepta el comando 2 primero llama al driver original 
para que haga el trabajo. En esta llamada ejecuta las dos rutinas del 
driver original, tanto la de estrategia como la de interrupción. A la 
vuelta reemplaza el puntero al BPB por uno a un buffer propio, a donde 
copia la información devuelta por el driver. De ahí calcula la cantidad 
de sectores por cluster y la reduce en uno o en dos cuando el 
dispositivo tiene un sólo sector por cluster, como es el caso en los 
diskettes de alta densidad. Después de esto da por terminada la rutina, 
como corresponde con un RETF. Ese mismo RETF es el que conforma el 
código de la rutina de interrupción que queda así reducida a una sola 
instrucción.

Otro caso se presenta cuando el virus intercepta un pedido de alguno de 
los comandos de escritura, el 8 o el 9. Una rutina especial del virus 
lleva un control que le dice si el disco pudo haber cambiado y lo 
primero que hace al recibir uno de estos comandos de escritura es 
llamarla.

En el caso en que el disco no haya cambiado, el virus determina si 
alguno de los sectores a grabar corresponde a un directorio. Hace esto 
de una manera simple y efectiva que consiste en controlar cada 20h bytes 
los bytes con offsets 8, 9 y 10 para ver si ahí dice EXE o COM.

En caso afirmativo interpreta coherentemente la doble word con offset 
1Dh como el tamaño del archivo ejecutable y si el presunto archivo no es 
demasiado grande ni demasiado chico ni tampoco es un subdirectorio o un 
archivo de sistema, lo que hace es reemplazar el número del primer 
cluster (word en el offset 1Ah) por el número del último cluster de la 
zona de datos del disco en donde, como ya veremos, se encuentra él. 
Encripta el número de cluster donde realmente empieza el archivo y lo 
salva en la word con offset 14h de la entrada del directorio; esos dos 
bytes de la entrada del directorio no son utilizados por DOS.

A partir de aquí, el virus ya sabe si una entrada de directorio ha sido 
previamente infectada, porque eso ocurre cuando el número del primer 
cluster coincide con el número del último cluster del disco (en donde se 
encuentra el virus).

La misma rutina que utiliza para encriptar un sector de directorio la 
emplea para desencriptarlo. Uno u otro modo de funcionamiento lo elige 
con un parámetro pasado en DL.

Cada vez que avanza 20h bytes por el sector para ubicarse en una 
(potencialmente) nueva entrada de directorio, vuelve a cambiar la clave 
de encriptación y sigue así hasta agotar todos los sectores que había 
que escribir. Luego llama a la INT 13h, utilizando para esto la entrada 
que determinó durante la instalación, y escribe el sector en disco.

A continuación toma una precaución importantísima. Como para encriptar 
el directorio tuvo que modificar los datos en RAM antes de grabarlos, 
ahora debe volver a desencriptarlos así el dueño del buffer no podrá 
siquiera sospechar que algo malo ha ocurrido. Resuelve la 
desencriptación cambiando el valor del flag pasado en DL a la rutina de 
encriptación. Luego de eso sí devuelve el control.

Todo esto ocurre en el caso en que el disco no haya cambiado desde la 
última intervención del driver. Si el disco pudo haber cambiado, el 
virus llama al driver original. El código que se ejecuta luego de esto 
es el mismo para el caso de un comando de escritura que para uno de 
lectura. La diferencia es que si es de escritura primero llama al driver 
original. Luego de esto las rutinas para lectura o escritura se juntan 
en una. Esta es una característica del modo en que el DIR II ha sido 
concebido. Todo el tiempo uno tiene la sensación de que el programador 
ha encontrado la forma de optimizar al máximo la utilización del código 
sin apartarse de sus propios valores estéticos.

El código que se ejecuta en cualquier operación de entrada/salida, 
comienza leyendo el primer sector del disco y forzando a continuación 
una llamada alcomando 2 "Build BPB".

Con los datos del BPB calcula la cantidad de sectores de datos que hay 
en el disco. Este cálculo lo hace restando de la cantidad total de 
sectores, los que utiliza el sector boot, los reservados para el 
directorio y los reservados para la FAT. De ahí calcula la cantidad de 
clusters para datos, restando uno, o dos en el caso de clusters de un 
solo sector.

Luego calcula la ubicación en la FAT del último sector de datos y usa la 
entrada correspondiente de la FAT para calcular la clave de encriptación 
utilizada en la rutina que describimos arriba.

Todo este manejo con la FAT es delicado porque algunas FATs son de 12 
bits y otras de 16. Si el último sector estaba marcado como malo o 
reservado, retrocede al sector anterior.

Luego marca la entrada de la FAT con el valor FFE0h, 0FFEh o FFFEh 
(según se trate de 12 o 16 bits) que significa último cluster del 
archivo y se fija si la FAT ya había sido cambiada del mismo modo en 
otra oportunidad. En el caso en que esté modificando la FAT por primera 
vez, sabe que se trata de un disco sano. Entonces salva la FAT 
modificada y luego se copia a sí mismo al final del disco.

Luego de esto el código se junta con la misma rutina que se ejecutaba 
cuando el disco no había cambiado. Solamente que ahora un flag en CH le 
avisa a la rutina que no debe salvar ningún sector en el disco.

Cómo funciona todo esto

Los detalles que acabamos de exponer se pueden resumir en pocas 
palabras. Lo que hace el DIR II es alojarse en el final del disco 
infectado y cambiar en las entradas de los directorios el número del 
primer cluster de todos los programas del disco para que apunten al 
cluster ocupado por él. Así, cuando llamamos un programa cuya entrada de 
directorio haya sido modificada, el DOS va a cargar el virus y no 
nuestro programa. El virus va a aprovechar esto para verificar si está 
instalado y después va a llamar al programa invocado obteniendo su 
nombre del environment.

Además de modificar las entradas de los directorios, el DIR II tiene que 
modificar también la FAT para que cuando llamemos a un programa el DOS 
no cargue más que un cluster: el último del disco.

Para saber cómo llamar al programa interceptado, el DIR II guarda en la 
misma entrada del directorio, en el offset 14h, el verdadero número de 
cluster donde el programa comienza. La cosa es complicada porque ese 
número está encriptado con un algoritmo que aunque es simple utiliza una 
clave cambiante.

Para instrumentar todo esto, el DIR II se cuela en el device driver del 
disco de arranque desde donde intercepta todas las operaciones 
elementales de entrada/salida realizadas por DOS. A todo esto el DIR II 
es capaz de esconder tanto su código residente como su código en el 
disco infectado. El código en RAM se confunde con la zona de datos del 
DOS. El código en disco lo disimula fácilmente porque el virus no 
infecta a los archivos, que permanecen intactos, solamente cambia el 
número del primer cluster en las entradas de directorio. Y por si fuera 
poco, completa su autonomía haciendo llamadas directas a los vectores 
originales de las interrupciones 21h y 13h.

El virus en sí no intenta ser dañino pero sin embargo es muy peligroso. 
Como la cantidad de bytes que ocupan los programas forma parte de las 
entradas de directorio y esto no concuerda con la cantidad de clusters 
reservados en la FAT, si ejecutamos el comando CHKDSK cuando el virus no 
está residente vamos a obtener una gran cantidad de cadenas perdidas. Si 
intentamos reparar este aparente error de alocación, vamos liberar todos 
esos clusters y como resultado vamos a perder los programas del disco.

El DIR II es además un ejemplo de un virus que no podemos simplemente 
limpiar de nuestras máquinas. Porque si no está activo no tenemos forma 
de acceder a los programas, ya que sólo él está en condiciones de 
reponer el número del primer cluster donde empiezan. La única forma de 
deshacerse del DIR II es renombrando primero todos los programas para 
que no terminen ni en EXE ni en COM. Al hacer esto, dado que el virus 
solamente toca los archivos con esas extensiones, sí vamos a poder 
borrarlo del disco. Hacerlo antes sería un error.

Leandro Caniglia es Doctor en Ciencias Matemáticas, Profesor Adjunto en 
FCEN (UBA) e Investigador Asistente del CONICET. Puede ser contactado en 
internet en caniglia@mate.edu.ar o en leandro@ubik.satlink.net y en 
FidoNet 4:901/303.4.