Análisis de virus: DIR II
por Leandro Caniglia
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.