jueves, septiembre 21, 2017

Hacking Wi-Fi: Cómo funciona el "Salto de Canal" (Parte 2 de 2)

Una vez que hemos repasado todos estos entresijos de la parte teórica del proceso de "Salto de Canal" en las redes WiFi que vimos en la primera parte de este artículo, ya estamos preparados para crear una pequeña herramienta que realice ese trabajo, así que vamos a ver en esta parte cómo hacerlo. Comencemos con las problemáticas que hemos visto anteriormente una a una.

Figura 16: Hacking Wi-Fi: Cómo funciona el "Salto de Canal" (Parte 2 de 2)

A la hora de hacer un sniffing, tenemos que poder “escuchar” en todos los canales para poder capturar todos los paquetes que viajan por el aire, pero como esto no es posible, puesto que las interfaces inalámbricas tan solo pueden estar escuchando en un canal concreto en un momento determinado, lo que tenemos que hacer es ir cambiando de canal a intervalos reducidos de tiempo. De esta forma, aunque no estemos en todos los canales, virtualmente parecerá que sí.

También es importante recordar que la técnica de salto de canal no persigue capturar en sí todos los paquetes que viajan por el aire, más bien persigue el objetivo de localizar o detectar los dispositivos vivos que están trasmitiendo por el medio. Y, una vez localizados, centrarse en un objetivo concreto, es decir, realizar el sniffing a un solo canal. Por lo comentado hasta ahora, el tiempo que pasemos en un canal es muy importante, de ahora en adelante lo llamaremos “timming”.

Es importante ser conscientes de que muchas estaciones o clientes no están continuamente transmitiendo. Por lo que debemos de utilizar un “timming” lo suficientemente pequeño para barrer toda la banda de frecuencia, pero al mismo tiempo lo suficientemente grande para llegar a capturar los paquetes transmitidos. En las PoCs que he creado he utilizado un “timming” de 0.4 y 0.6. Pero es posible utilizar tiempos menores.

El solapamiento

A la hora de ir saltando de canal, lo más coherente como primera impresión, por sencillez, sería pensar en recorrer mediante un bucle todos los canales de formas secuencial. Es decir, recorrer los canales desde el 1 pasando por el 2, 3, 4... hasta recorrer los 14 canales. Sin embargo, esto no sería lo más eficiente. Como comentamos en los puntos anteriores, la banda de 2.4 Ghz tiene el inconveniente de los solapamientos entre canales - peor todavía si se utiliza el estándar IEEE 802.11n que utiliza un ancho de banda de 40 Mhz -. El solapamiento provoca que sea posible capturar tráfico que pertenece a los otros canales contiguos. Esto no interesa.

Para evitar este problema se suele ir saltando, como mínimo, de tres en tres canales. Si se analiza el funcionamiento de airodump-ng se puede corroborar que sigue un mecanismo basado en saltos no secuenciales.

Figura 17: Arrays de canales en airodump-ng

En el “header” de airodump-ng (aquí el C) se pueden ver los ARRAYs de las listas predefinidos con saltos de canales no secuenciales según el estándar utilizado. Está sería una de las formas correctas. Es posible, utilizando una imagen que muestre los solapamientos entre canales de los estándares IEEE 802.11 bgna buscar otra serie de saltos sin solapamiento.

Canales comunes y no comunes

Otro de los problemas que podemos encontrarnos es que, según la región o el país, vamos a poder utilizar unos canales u otros, dependiendo de la interfaz que se utilice y de la configuración de la región. Por lo que al definir una lista con los canales debemos de tener en cuenta cuales son los canales comunes a todos los países y cuáles no.

Ordenarlos de forma que si un canal no común no se utiliza no rompa la eficiencia ante el solapamiento de la lista. Es decir, si tenemos la siguiente lista: 1, 14, 2. Y no utilizamos el canal 14. Al final se va a realizar el salto del canal 1 al 2 y esto no sería correcto al ser canales contiguos y solapables. Recordad que los canales comunes en todos los países van desde el 1 hasta el 11. El 12 y 13 se utilizan en la mayoría de países y el 14 solo en Japón.

¿Qué canales?

Por el mismo problema que el caso anterior, es posible que no todos los canales funcionen al realizar el cambio de canal (aparezcan errores). Por lo que se estaría realizando saltos a canales no válidos. La solución más óptima, en mi opinión, sería comprobar antes de realizar el sniffing que canales son válidos y cuáles no.

¿Qué bandas?

Lo mismo que el caso anterior, pero en esta ocasión referente a las bandas de frecuencia y a los canales. También se podrá solucionar haciendo una comprobación previa.

Mi módulo, SaltoCanal

El objetivo que se persigue es crear un módulo independiente que pueda ser utilizado por otras herramientas de forma totalmente modular y abstracta. Que esté pensando para utilizar en una Raspberry Pi con la interfaz inalámbrica que trae de serie bajo la distribución modificada de Kali Linux. Como lenguaje de programación he decido utilizar Python. Por diversos motivos. Entre ellos:

  • Es el recomendado por los fundadores de la Raspberry Pi.
  • Por ser un lenguaje de programación de alto nivel. Un lenguaje de sintaxis sencilla y clara.
  • Es un lenguaje con gran documentación y herramientas.
  • Es fácil de aprender. Cualquiera que no conozca el lenguaje pero que sepa programar puede comprender fácilmente el código. Por lo que facilita el aprendizaje a terceros.
  • Es un lenguaje interpretado o de script, fuertemente tipado y dinámico, es multiplataforma y es orientado a objetos.
  • Además, es un lenguaje bastante potente y con muchas librerías que nos ayudan a realizar casi cualquier cosa.

Otro de los motivos más importantes es que se integra muy bien con Scapy. Librería que estoy utilizando para el desarrollo del proyecto que tengo entre manos. Aquí os dejo el enlace a GitHub del código del script:


Figura 18: Módulo salto_canal.py

Este código está sujeto a cambios, no puedo decir que esté testeado a fondo. Por lo que seguro que habrá que corregir algunas cosas.

SaltoCanal

Utiliza los siguientes módulos:

  • Thread de threading.
  • Os.
  • Sys.
  • Popen, PIPE de subprocess.
  • SIGINT, signal de signal.
  • Sleep de time.

Y un módulo propio, manejo_interfaz, para realizar ciertas operaciones que son comunes sobre las interfaces inalámbricas. Las constantes que utiliza son dos listas de canales ordenadas mediante un sistema ideal para evitar el solapamiento:

  • LISTA_CANALES_24GHZ = [1, 6, 11, 14, 2, 7, 3, 8, 4, 12, 9, 5, 10, 13]
  • LISTA_CANALES_5GHZ = [36, 38, 40, 42, 44, 46, 52, 56, 58, 60, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165] DN = open(os.devnull, 'w')

Esta constante se utilizará para descartar la salida cuando se invoque a popen.

La clase SaltoCanal

El modulo es una clase que hereda de threading.Thread. Sus métodos son:

Métodos públicos:

 __init__: Para instanciar el objeto.
 comprobar_canales: Para comprobar que canales podemos utilizar.
 fijar_canal: Para fijar la interfaz en un canal concreto.
 tiempo_canal: El “timming”, el tiempo que se utiliza un canal al realizar el salto de canal.
 lista_VIC: Very Important Channel. Crea una lista con los canales más importantes para el usuario.
 fijar_canales: Para fijar los canales en una lista dada por el usuario.
 pausar: Para pausar el hilo.
 continuar: Para continuar con el hilo si está pausado.
 terminar: Para finalizar el hilo.
 run: Para ejecutar el hilo.

Métodos privados:

 __get_pausar: Devuelve la variable pausa.
 __lista_canales_valida: Para comprobar que los datos de entrada del __init__ son correctos.
 __comprobar_interfaz: Comprueba que existe la interfaz proporciona por el usuario y que está se encuentra en modo MONITOR o RFMON.
 __cambiar_canal: Método que cambia el canal de la interfaz mediante un proceso.
 __comprobar_canales: Comprueba los canales válidos que se pueden utilizar y devuelve una lista de ellos.

Pasemos ahora a detallar cada método.

__init__()
Como ya sabréis es un método reservado para instanciar los objetos de la clase (el constructor en Java). Por ejemplo, sc = SaltoCanal(“wlan1”). A este método solo es obligatorio pasarle la interfaz. Todas las demás variables son optativas.
  • La variable espera se utiliza para indicar el tiempo de escucha en un canal concreto.
  • La variable check se utiliza para realizar la comprobación de canales. Es decir, para verificar aquellos que funcionan y descartar los que no.
  • Las variables _24Ghz y _5Ghz indican que listas de bandas de frecuencia se van a utilizar.
  • Por último, verbose, para que la clase imprima por pantalla información al respecto.
Como el objetivo es utilizar esta clase desde otros scripts puede resultar más eficiente que sea mudo. Por lo que nos bastará por comprobar los códigos de salida para conocer el motivo. Dentro del método se verifican que los datos de entrada sean coherentes, es decir, que al menos se pase una de las bandas de frecuencia, esto se hace con el método privado: “__lista_canales_valida()”. 
También comprobamos que la interfaz facilitada por el usuario es correcta o existe y está en modo monitor o RFMON. Para ello se llama al método privado “__comprobar_interfaz()” que a su vez utilizará otros métodos (existe_interfaz() y esta_modo_monitor()) utilizando un módulo externo que fue creado por mí y que tiene como nombre manejo_interfaz. Con ellos verificaremos que existe la interfaz y que está en modo monitor. Miraros este módulo para ver cómo se comprueban estas cosas. 
Cabe resaltar que es aquí donde se inicializa el hilo (“Thread”) y que se crea como daemon. Que básicamente es para que el hilo principal pueda terminar sin esperar a nadie más. Se inicializan el resto de variables que utilizaremos para el correcto funcionamiento del hilo.
comprobar_canales()
Este método se utiliza para comprobar que canales se pueden utilizar y cuáles no. Por lo que devolverá una lista con los canales válidos. Este proceso solo se realiza si el check está a True. Para ello, utiliza el método privado “__comprobar_canales()”.
__comprobar_canales():
Este método recorre la lista de canales que se le pasan para verificar si son válidos. Utilizando el método “__cambiar_canal()” que devuelve True o False si existen algún error. En caso de no existir, se guarda en una lista el canal, en caso contrario, se descarta.
También comprueba si todos los canales han fallado, por lo que si esto ocurre el programa finaliza con un error, puesto que no existen canales válidos.
__cambiar_canal():
Este método recibe un canal y lanza un Popen. Un Popen es un módulo que nos permite lanzar programas externos y tener cierto control sobre ellos. Mediante la variable del módulo “stderr” comprobamos si existen errores. 
Como se puede observar, estamos utilizando iw para realizar el cambio de canal, como comentamos anteriormente. La sintaxis sería: “iw dev self.interfazset channel canal”, siendo la variable interfaz la facilitada por el usuario, y canal el canal que queremos utilizar. No hay más magia en todo esto.
fijar_canal()
Este método permite fijar un canal en vez de andar saltando de canales. Solo funcionará si el hilo está parado.
tiempo_canal()
Este método permite cambiar el “timming”, el tiempo que pasaremos utilizando un canal.
fijar_canales()
Este método fija el salto de canales a una lista proporcionada por el usuario. Puede resultar útil cuando el usuario tan solo quiere realizar un sniffing a tan solo a una pequeña lista de canales concretos o fijar una lista de canales propia.
lista_VIC()
Very Important Channel. Este método me parece también muy interesante. Como comentamos en la teoría, existen canales más importantes que otros, como son el canal 1, 6 y 11 que no se solapan entre ellos, por lo que puede interesar pasar más tiempo en ellos porque podrían estar más poblados. 
Bueno, en realidad, este es uno de sus usos, pero puede tener muchos dependiendo de la imaginación de cada uno. Al habilitar este método y proporcionarle una lista y un tiempo determinado, el hilo detectara los canales que pertenezcan a esta lista y esperara el tiempo facilitado por el usuario.
run()
Este es un método de la clase “Thread”. Una vez que se lance el hilo se ejecutará lo que está dentro de este método. En nuestro caso un bucle que recorre infinitamente una y otra vez toda la lista de canales, provocando el salto de canal.
sc = SaltoCanal(“wlan1”, check=True, _24Ghz = True, _5Ghz=True, verbose=True) sc.start()
PoC – Proof of Concept.

Una vez que tenemos creado nuestro “bicho” es hora de comprobar que todo funciona correctamente. Para ello, ponemos la interfaz en modo monitor. En los ejemplos estoy utilizando una distribución de Kali Linux un tanto peculiar. Esta distribución modificada, entre otras cosas, permite poner la interfaz Wi-Fi que viene por defecto con la Raspberry Pi 3 en modo monitor.

Hablaros sobre todo esto escapa del objetivo principal de este artículo. Para los interesados aquí os dejo ciertas referencias al proyecto NexMon  y la URL donde podéis descargaros las imágenes de Kali Linux modificada. Para poner la tarjeta de la Raspberry Pi en modo monitor tan solo tenemos que utilizar este comando: monstart Para parar el modo monitor, bastaría con: monstop

Como veis, todo muy sencillo, pero ¡ojo!, si se utilizan varias tarjetas inalámbricas, hay que cerciorarse bien cuál es la interfaz que está en modo monitor. Se puede hacer con iwconfig o iw dev

Una vez que se tiene la interfaz en modo monitor se puede comprobar que todo funciona correctamente utilizando un sniffer. Existen varias posibilidades, yo me he decantado por Wireshark por venir ya preinstalado, por ser un viejo conocido (aka Ethereal) y por la interfaz gráfica, para que todo sea más visual en las capturas de pantalla. Para realizar el sniffing con Wireshark antes se debe de indicar la interfaz por la cual se quiere capturar tráfico. Es decir, la interfaz que esté en modo monitor. Si todo es correcto, irán apareciendo los paquetes capturados en Wireshark.

Probando salto_canal.py.

Existen varias formas de lanzar este módulo. La primera forma es simplemente ejecutándolo directamente con python saltocanal.py. Recordad que si utilizáis este método deberéis de crear en el “main” algo como esto:
sc = SaltoCanal(“wlan1”) 
sc.start() 
raw_input('Presiona enter para finalizar...')
El raw_input es fundamental, de lo conterario el programa y el hilo finalizaran y no se podrá testear. Pero si quieres también puedes probarlo ejecutándolo a través de la consola de Python. Para ello se lanza la consola de Python mediante un comando python y desde allí de importa el módulo. (Para ello se aconseja lanzar la consola de python en el mismo directorio que salto_canal.py). Y se prueban los métodos. Como se aprecia en las siguientes capturas, todo parece funcionar correctamente.

Figura 19: Preobando el módulo de Salto de Canal en Python en una Raspberry Pi 3

Conclusiones:

Como hemos visto, crear un script que realice el salto de canal no es complejo. Lo fundamental es conocer los problemas que pueden aparecer y buscarle soluciones óptimas. Esto solo es posible analizando y estudiando el funcionamiento de las cosas. Gracias al Open Software existen multitud de herramientas de código abierto que pueden ayudarnos en este proceso.

A la hora de auditar una red WLAN puede que las herramientas más conocidas no nos sirvan. Bien por ser algo muy concreto y especifico o porque necesitamos automatizar el proceso. Crear nuestras propias herramientas nos facilitará muchísimo las cosas, además de permitirnos tener un mayor control de lo que estamos haciendo.

Crear herramientas modulares y abstractas nos facilitarán la reutilización de las mismas en casi cualquier escenario. Incluso pueden darnos nuevas ideas. Una vez solucionado el problema del salto de canal, ya se puede empezar a jugar con: python + scapy + dot11 y dejar volar nuestra imaginación. ¡¡happy hacking!!. Si tenéis cualquier inquietud no dudéis en poneros en contacto conmigo.

Autor: Enrique Andrade - NETTinG

3 comentarios:

  1. ¿Por qué alguien iba a querer usar este script teniendo en cuenta que ya existe airodump-ng y está testado y se sabe que funciona perfectamente?

    ResponderEliminar
  2. ¿Por aprender a hacerlo uno mismo?

    ResponderEliminar
  3. Python 2 supongo. ¿Algún problema en que intente portarlo a python3 para debian?

    ResponderEliminar