Introducción

KeeLoq es un protocolo de seguridad basado en un algoritmo criptográfico de clave simétrica que se utiliza principalmente en sistemas de control remoto, como los mandos de puertas de garaje y sistemas de acceso remoto para automóviles. Fue desarrollado por la empresa Microchip Technology y es ampliamente utilizado debido a su bajo costo y relativa simplicidad de implementación.

KeeLoq implementa un sistema de “código rodante” para prevenir ataques de reproducción (replay attacks). Esto significa que cada vez que se presiona el botón del mando, se genera un código único que nunca se repite. El receptor (la puerta del garaje, por ejemplo) reconoce este código y lo valida contra una secuencia esperada, ignorando cualquier código duplicado.

Tanto el mando como el receptor comparten una clave secreta de 64 bits. Esta clave se utiliza para cifrar y descifrar los mensajes intercambiados. El algoritmo utiliza operaciones simples como XOR, desplazamientos y rotaciones para su funcionamiento.

Al inicio el mando y el receptor son emparejados compartiendo la misma clave secreta y un contador inicial. Cuando el usuario presiona un botón, el mando genera un código cifrado basado en la clave secreta del fabricante y el contador y este código es transmitido al receptor. El receptor descifra el mensaje utilizando la clave compartida y compara el contador recibido con su propio registro. Si el código es válido y el contador está dentro de un rango permitido (para tolerar presiones accidentales fuera del alcance), la puerta se abre. Después de una operación exitosa, tanto el mando como el receptor actualizan su contador para evitar reutilización de códigos.

El caso mencionado en el que se utiliza una única clave de fabricante se conoce como Simple Learning y es el que se va a tratar en este artículo. Existe también el método Normal Learning en el que la clave de cifrado se genera a partir de la clave del fabricante y del número de serial del transmisor. Por último el método más seguro es el Secure Learn en el que la clave de cifrado se deriva de una semilla (seed) presente en el transmisor y que debe ser registrada en el receptor.

La frecuencia utilizada usualmente por los mandos KeeLoq es la de 433.920 MHz, la banda reservada para ISM, con una modulación AM OOK. Podremos capturar la señal con un dispositivo de radio definida por software como el RTL-SDR o con el Flipper Zero. Después podremos decodificar la señal utilizando la herramienta Universal Radio Hacker. Finalmente descifraremos la parte cifrada de la señal con la clave del fabricante para obtener el contador del mando. Como referencia nos vamos a basar en la hoja de datos del chip codificador HCS301.

Captura de la señal

Método 1: RTL-SDR

Con el dispositivo USB conectado, utilizaremos la herramienta para capturar y analizar la señal. Para ello accederemos al menu File > Record Signal.... Seleccionamos el dispositivo RTL-SDR, su identificador, la frecuencia 433,92M, el ancho de banda 250,0K y la ganancia y corrección de frecuencia correspondiente. Capturamos la señal pulsando en el botón Start. Observamos varias tramas capturadas. Guardamos el archivo de la captura con el botón Save... y la captura se cargará automáticamente en el programa al cerrar la ventana. Ahora debemos de ajustar la señal capturada ya que puede contener ruido. El valor del ruido Noisesuele ser automáticamente rellenado por el programa. En este caso no hay ruido así que se mantiene en 0. Ampliamos la imagen ajustándola a una de las tramas y ampliamos la escala utilizando el menú Y-Scale. Cambiamos el valor del umbral Center a 0,0000. A continuación cambiamos la vista Signal view de Analog a Demodulated y ajustamos de nuevo el valor Y-Scale. En esta vista observamos que la señal se considera como un 1 si la señal se encuentra en estado alto (HIGH) y un 0 en estado bajo (LOW). El decodificador mostrará un 0 para todas las señales por debajo de la sección verde y un 1 en la sección morada. Podemos ajustar el valor de Noise para poder diferenciar las dos señales. En este caso se ajusta el valor de Noise a 0,0000. Ahora filtramos una de las tramas y la recortamos mediante clic derecho y Crop to Selection. Ahora necesitamos hallar el valor Te, que es valor de la señal con menor duración. La seleccionamos y observamos el valor. Vemos que la señal con menor duración contiene 100 muestras (samples) con una duración de 400 microsegundos. Podemos hallar la velocidad con la que se envían los datos dividiendo el valor de 1 segundo entre el Te, lo que da un valor de 2500 bps (baudios por segundo). En la tabla 3.3 de la hoja de datos observamos que 400 microsegundos es la tasa de transmisión por defecto. Podemos utilizar el valor de 100 muestras en el campo Samples/Symbol del programa. Vamos a activar una tolerancia a errores del 5% en el campo Error tolerance a 5. Mantenemos la modulación Modulation a ASK y los bits por símbolo Bits/Symbol a 1. Observamos los valores decodificados:

10101010101010101010101

1001001101001001101001001001101101001001101001001101101101001101101101001001101101001101001101101001101101001101101101101001001101101101101101101101101101101101101101101101101101101101001101101001

Método 2: Flipper Zero

En el dispositivo Flipper Zero utilizamos la aplicación Sub-GHz en la opción de menú Read. En la aplicación, accedemos a la configuración pulsando en el botón izquierdo y ajustamos los valores de frecuencia 433.92 y modulación AM con ancho de banda de 270 KHz AM270. Volvemos a la aplicación con el botón atrás y capturamos la señal del mando. Obtenemos la señal del mando KeeLoq KL del fabricante GarajeA y el mensaje demodulado DB9B119490....

Formato de la señal

En la hoja de datos, en la sección 4.1 y 4.2 junto a las figuras 4.1 y 4.2 se detalla el formato de la señal. La palabra del código de KeeLoq se compone de varias partes. En primer lugar observamos que dos Te en estado alto y uno en estado bajo (110) se corresponde con el bit 0 en el momento de la decodificación. Un Te en estado alto y dos en estado bajo (100) se corresponde con el bit 1. Cada palabra de código contiene un preámbulo con un ciclo de trabajo del 50%, una cabecera, 32 bits de datos cifrados y 34 bits de datos fijos seguidos por un período de guarda antes de que puede empiece otra palabra de código (66 bits en total). Los 32 bits de los datos cifrados se generan a partir de 4 bits de botón, 12 bits del discriminador, que es un valor utilizado para comprobar si el descifrado ha sido correcto y contiene los 10 bits menos significativos del número de serie, y el valor de sincronización (contador) de 16 bits, que es actualizado cada vez que se pulsa un botón. Los 34 bits de los datos fijos se componen de 2 bits de estados (si el mensaje enviado esta repetido y si el voltaje recibido es bajo), 4 bits de botón (S0, S1, S0, S3 conectados cada uno a una entrada del chip) y el número de serie de 28 bits. En el caso de que el transmisor utilice Secure Learn mediante la pulsación de todos los botones del mando, se enviará la semilla (seed), no siendo este el caso. Volviendo a Universal Radio Hacker podemos observar la semejanza de la señal. La palabra de código que se muestra en el diagrama va de MSb (bit más significativo) a LSb (bit menos significativo) por lo que tendremos que invertir los bits demodulados, obteniendo en primer lugar la porción cifrada y luego la porción fija.

Decodificación de la señal

Flipper Zero

Volviendo al Flipper Zero, podemos observar los valores decodificados decodificados y la sección cifrada descifrada ya si disponemos de la clave del fabricante el descifrado se realiza automáticamente. Los valores obtenidos son:

  • Key:DB9B119490C00004: bits en hexadecimal demodulados sin decodificar
  • Fix:0x20000309: bits de la porción fija invertidos desde 0x90C00004
  • Cnt:0401: bits obtenidos de la porción cifrada que se corresponden con el contador (valor de sincronización) con el valor hexadecimal 401, o 1025 en decimal
  • Hop:0x2988D0DB: bits de la porción cifrada invertidos desde 0xDB9B1194, sin descifrar
  • Btn:2: bits con el botón presionado en el mando (2)
  • MF:GarajeA: fabricante con el cuál se ha descifrado la porción cifrada del mensaje, en este caso con la clave de fabricante de GarajeA

La clave de fabricante utilizada es AABBCCDDEEFFAABB, de 64 bits. Es almacenada en el archivo keeLoq_mfcodes_user en el directorio /ext/subghz/assets, con el siguiente formato. Las claves se almacenan con el formato clave:tipo_de_cifrado:nombre_del_fabricante. Siendo tipo_de_cifrado, 1 para Simple Learning, 2 para Normal Learning y 3 para Secure Learn.

Filetype: Flipper SubGhz Keystore File
Version: 0
Encryption: 0
AABBCCDDEEFFAABB:1:GarajeA

Universal Radio Hacker

Volviendo a Universal Radio Hacker, con la señal demodulada en bits podemos movernos a la pestaña Analysis. Observamos dos mensajes en las filas, el preámbulo y el mensaje a su continuación. En Encoding se muestra que el mensaje está siendo decodificado utilizando el método Non Return To Zero (NRZ) en el que el estado alto se interpreta como un 1 y el estado bajo con un 0. Como vimos anteriormente tenemos que transformar las porciones 110 a 0 y 100 a 1. Para ello pulsaremos en la opciones de Decoding y en la opción ....

Con ello se abre una ventana llamada Decoding. Manteniéndonos en la opción Non Return To Zero (NRZ) arrastramos la función Substitution de Base Functions a Your Decoding. Configuramos en la opciones 2 columnas (Rows) en introducimos las opciones 110->0 y 100->1 en la tabla. Finalmente guardamos el decodificador con el nombre KeeLoq pulsando el botón Save as... . Cerramos la ventana para volver a la ventana Analysisy seleccionamos el decodificador Decoding: KeeLoq en el mensaje de la segunda fila y obtenemos el mensaje de 66 bits en columnas. El valor obtenido en binario es 110110111001101100010001100101001001000011000000000000000000010011, en hexadecimal db9b119490c000043, coincidiendo con el decodificado por el Flipper Zero. Configurando la sección inferior podemos obtener los valores de la porción cifrada 2988d9db y la porción fija 320000309. Observamos que en el Flipper Zero, se está ignorando la primera cifra (3) del código fijo, relativa a los bits de batería baja y repetición. Desciframos la porción cifrada con la librería leekoq de Python.

$ python -m virtualenv KeeLoq
$ pip install leekoq
$ python
>>> import leekoq
>>> key = 0xaabbccddeeffaabb
>>> uncipher = leekoq.decrypt(0x2988d9db, key)
>>> print(hex(uncipher))
0x23090401

Obtenemos el valor en hexadecimal 23090401 que en binario son los 32 bits 00100011000010010000010000000001. Su decodificación es la siguiente:

2           3     0   9    0   4   0   1
_  _  _  _  --  --____---- ____----____----
0  0  1  0  00  1100001001 0000010000000001
S2 S1 S0 S3 OVR DISCRIMINA CONTADOR

El indicador de S0 está establecido a 1, indicando el uso del botón 2. El valor en binario del contador es 0000010000000001, 401 en hexadecimal y 1025 en decimal. El valor del discriminador es 1100001001, que debe coincidir con los últimos 10 bits del número de serie.

Moviéndonos a la porción fija, su valor en binario son los 34 bits 1100100000000000000000001100001001. Su decodificación es la siguiente:

3           2           0   0   0   0   3   0   9
-      -    -  -  -  -  ____----____----____----____
1      1    0  0  1  0  0000000000000000001100001001
Repeat Vlow S2 S1 S0 S3 NUMEROSERIAL

El indicador de Repeat está establecido a 1, indicando que es un mensaje repetido. El indicador de Vlow está establecido a 1, indicando que el dispositivo necesita un cambio de batería. El indicador de S0 está establecido a 1, indicando el uso del botón 2. El valor en binario del número de serie es 0000000000000000001100001001, 309 en hexadecimal y 777 en decimal. Confirmamos que el valor del discriminador previo es correcto.

Conclusión

La estructura del protocolo KeeLoq permite la generación de 2^32 (4000 millones) de porciones cifradas diferentes con una clave de fabricante de 64 bits que permite la existencia de 2^64 (18 trillones) de claves diferentes. La combinación de la parte fija con la variable permite la existencia de 2^66 (76 trillones) códigos diferentes. Esto hace prácticamente imposible un ataque de fuerza bruta sin obtener previamente la clave de fabricante o mediante criptoanálisis en el chip. Mediante el uso de Normal Learning o Secure Learn el proceso de cifrado será más robusto.

Existen investigaciones como Algebraic and Slide Attacks on KeeLoq que muestran ataques hacia la implementación del cifrado de KeeLoq. Lo que hace recomendable moverse a otros sistemas basados en cifrado AES o sistemas de clave asimétrica.